diff --git a/src/main/java/org/ays/auth/exception/AysUserAlreadyPassiveException.java b/src/main/java/org/ays/auth/exception/AysUserAlreadyPassiveException.java new file mode 100644 index 000000000..b56b34a7f --- /dev/null +++ b/src/main/java/org/ays/auth/exception/AysUserAlreadyPassiveException.java @@ -0,0 +1,25 @@ +package org.ays.auth.exception; + +import org.ays.common.exception.AysConflictException; + +import java.io.Serial; + +/** + * Exception thrown when a user is already in a passive state. + */ +public final class AysUserAlreadyPassiveException extends AysConflictException { + + /** + * Unique identifier for serialization. + */ + @Serial + private static final long serialVersionUID = 2484662602911824448L; + + /** + * Constructs a new {@link AysUserAlreadyPassiveException} with the specified detail message. + */ + public AysUserAlreadyPassiveException() { + super("user is already passive!"); + } + +} diff --git a/src/main/java/org/ays/auth/exception/AysUserNotActiveAuthException.java b/src/main/java/org/ays/auth/exception/AysUserNotActiveAuthException.java new file mode 100644 index 000000000..0f6eadd72 --- /dev/null +++ b/src/main/java/org/ays/auth/exception/AysUserNotActiveAuthException.java @@ -0,0 +1,37 @@ +package org.ays.auth.exception; + +import org.ays.auth.model.enums.AysUserStatus; +import org.ays.common.exception.AysAuthException; + +import java.io.Serial; + +/** + * Exception thrown when attempting to authenticate a user that is not active. + */ +public final class AysUserNotActiveAuthException extends AysAuthException { + + /** + * Unique identifier for serialization. + */ + @Serial + private static final long serialVersionUID = -5218287176856317070L; + + /** + * Constructs a new {@link AysUserNotActiveAuthException}with the specified userId. + * + * @param userId the id of the user that is not active. + */ + public AysUserNotActiveAuthException(String userId) { + super("user is not active! userId:" + userId); + } + + /** + * Constructs a new {@link AysUserNotActiveAuthException} with the specified user status. + * + * @param status the status of the user that is not active. + */ + public AysUserNotActiveAuthException(AysUserStatus status) { + super("user is not active! currentStatus: " + status.name()); + } + +} diff --git a/src/main/java/org/ays/auth/exception/AysUserNotActiveException.java b/src/main/java/org/ays/auth/exception/AysUserNotActiveException.java index 647b70ac1..442f0bccd 100644 --- a/src/main/java/org/ays/auth/exception/AysUserNotActiveException.java +++ b/src/main/java/org/ays/auth/exception/AysUserNotActiveException.java @@ -1,37 +1,27 @@ package org.ays.auth.exception; -import org.ays.auth.model.enums.AysUserStatus; -import org.ays.common.exception.AysAuthException; +import org.ays.common.exception.AysConflictException; import java.io.Serial; /** - * Exception thrown when attempting to authenticate a user that is not active. + * Exception thrown when a user does not in an active state. */ -public final class AysUserNotActiveException extends AysAuthException { +public final class AysUserNotActiveException extends AysConflictException { /** * Unique identifier for serialization. */ @Serial - private static final long serialVersionUID = -5218287176856317070L; + private static final long serialVersionUID = 3508025652421021710L; /** - * Constructs a new UserNotActiveException with the specified userId. + * Constructs a new {@link AysUserNotActiveException} with the specified userId. * - * @param userId the userId of the user that is not active + * @param userId the id of the user that is not active. */ public AysUserNotActiveException(String userId) { super("user is not active! userId:" + userId); } - /** - * Constructs a new UserNotActiveException with the specified user status. - * - * @param status the status of the user that is not active - */ - public AysUserNotActiveException(AysUserStatus status) { - super("user is not active! currentStatus: " + status.name()); - } - } diff --git a/src/main/java/org/ays/auth/service/AysUserUpdateService.java b/src/main/java/org/ays/auth/service/AysUserUpdateService.java index 58189667b..d2323b2a4 100644 --- a/src/main/java/org/ays/auth/service/AysUserUpdateService.java +++ b/src/main/java/org/ays/auth/service/AysUserUpdateService.java @@ -1,5 +1,6 @@ package org.ays.auth.service; +import org.ays.auth.exception.AysUserAlreadyPassiveException; import org.ays.auth.exception.AysUserNotActiveException; import org.ays.auth.exception.AysUserNotExistByIdException; import org.ays.auth.exception.AysUserNotPassiveException; @@ -31,6 +32,7 @@ public interface AysUserUpdateService { * * @param id The unique identifier of the user to be passivated. * @throws AysUserNotExistByIdException if a user with the given ID does not exist. + * @throws AysUserAlreadyPassiveException if the user is already in a passive state. * @throws AysUserNotActiveException if the user is not in an active state. */ void passivate(String id); diff --git a/src/main/java/org/ays/auth/service/impl/AysAuthServiceImpl.java b/src/main/java/org/ays/auth/service/impl/AysAuthServiceImpl.java index 1f6153ac2..8ba6e3bff 100644 --- a/src/main/java/org/ays/auth/service/impl/AysAuthServiceImpl.java +++ b/src/main/java/org/ays/auth/service/impl/AysAuthServiceImpl.java @@ -6,7 +6,7 @@ import org.ays.auth.exception.AysPasswordNotValidException; import org.ays.auth.exception.AysUserDoesNotAccessPageException; import org.ays.auth.exception.AysUserIdNotValidException; -import org.ays.auth.exception.AysUserNotActiveException; +import org.ays.auth.exception.AysUserNotActiveAuthException; import org.ays.auth.model.AysIdentity; import org.ays.auth.model.AysRole; import org.ays.auth.model.AysToken; @@ -64,7 +64,7 @@ class AysAuthServiceImpl implements AysAuthService { * @return {@link AysToken} representing the access token generated upon successful authentication. * @throws AysEmailAddressNotValidException If the provided email address is not valid or does not exist. * @throws AysPasswordNotValidException If the provided password is not valid. - * @throws AysUserNotActiveException If the user's status is not active. + * @throws AysUserNotActiveAuthException If the user's status is not active. * @throws AysUserDoesNotAccessPageException If the user does not have permission to access the requested page. */ @Override @@ -125,7 +125,7 @@ private void validateUserSourcePagePermission(final AysUser user, * @param refreshToken The refresh token used to generate a new access token. * @return A new {@link AysToken} containing the refreshed access token. * @throws AysUserIdNotValidException If the user ID extracted from the refresh token is not valid. - * @throws AysUserNotActiveException If the user associated with the refresh token is not active. + * @throws AysUserNotActiveAuthException If the user associated with the refresh token is not active. */ @Override public AysToken refreshAccessToken(final String refreshToken) { @@ -151,15 +151,14 @@ public AysToken refreshAccessToken(final String refreshToken) { /** * Validates the status of the user. - * Throws {@link AysUserNotActiveException} if the user is not active. * * @param user The {@link AysUser} object whose status needs to be validated. - * @throws AysUserNotActiveException If the user is not active. + * @throws AysUserNotActiveAuthException If the user is not active. */ private void validateUserStatus(final AysUser user) { if (!user.isActive()) { - throw new AysUserNotActiveException(user.getId()); + throw new AysUserNotActiveAuthException(user.getId()); } } diff --git a/src/main/java/org/ays/auth/service/impl/AysUserPasswordServiceImpl.java b/src/main/java/org/ays/auth/service/impl/AysUserPasswordServiceImpl.java index 86d8489fc..508cd2336 100644 --- a/src/main/java/org/ays/auth/service/impl/AysUserPasswordServiceImpl.java +++ b/src/main/java/org/ays/auth/service/impl/AysUserPasswordServiceImpl.java @@ -2,8 +2,8 @@ import lombok.RequiredArgsConstructor; import org.ays.auth.exception.AysEmailAddressNotValidException; -import org.ays.auth.exception.AysUserNotActiveException; import org.ays.auth.exception.AysUserDoesNotAccessPageException; +import org.ays.auth.exception.AysUserNotActiveAuthException; import org.ays.auth.exception.AysUserPasswordCannotChangedException; import org.ays.auth.exception.AysUserPasswordDoesNotExistException; import org.ays.auth.model.AysRole; @@ -52,7 +52,7 @@ class AysUserPasswordServiceImpl implements AysUserPasswordService { * * @param forgotPasswordRequest the request containing the user's email address. * @throws AysEmailAddressNotValidException if the email address does not correspond to any existing user. - * @throws AysUserNotActiveException if the user status is not active. + * @throws AysUserNotActiveAuthException if the user status is not active. * @throws AysUserDoesNotAccessPageException if the user lacks permission to access the source page. */ @Override @@ -64,7 +64,7 @@ public void forgotPassword(final AysPasswordForgotRequest forgotPasswordRequest) .orElseThrow(() -> new AysEmailAddressNotValidException(emailAddress)); if(!user.isActive()) { - throw new AysUserNotActiveException(user.getStatus()); + throw new AysUserNotActiveAuthException(user.getStatus()); } this.validateUserSourcePagePermission(user); diff --git a/src/main/java/org/ays/auth/service/impl/AysUserUpdateServiceImpl.java b/src/main/java/org/ays/auth/service/impl/AysUserUpdateServiceImpl.java index 77195d970..a41492c78 100644 --- a/src/main/java/org/ays/auth/service/impl/AysUserUpdateServiceImpl.java +++ b/src/main/java/org/ays/auth/service/impl/AysUserUpdateServiceImpl.java @@ -5,6 +5,7 @@ import org.ays.auth.exception.AysUserAlreadyDeletedException; import org.ays.auth.exception.AysUserAlreadyExistsByEmailAddressException; import org.ays.auth.exception.AysUserAlreadyExistsByPhoneNumberException; +import org.ays.auth.exception.AysUserAlreadyPassiveException; import org.ays.auth.exception.AysUserIsNotActiveOrPassiveException; import org.ays.auth.exception.AysUserNotActiveException; import org.ays.auth.exception.AysUserNotExistByIdException; @@ -131,7 +132,8 @@ public void activate(String id) { * * @param id The unique identifier of the user to be passivated. * @throws AysUserNotExistByIdException if a user with the given ID does not exist. - * @throws AysUserNotActiveException if the user is not in an active state and cannot be passivated. + * @throws AysUserAlreadyPassiveException if the user is already in a passive state. + * @throws AysUserNotActiveException if the user is not in an active state. */ @Override public void passivate(String id) { @@ -140,6 +142,10 @@ public void passivate(String id) { .filter(userFromDatabase -> identity.getInstitutionId().equals(userFromDatabase.getInstitution().getId())) .orElseThrow(() -> new AysUserNotExistByIdException(id)); + if (user.isPassive()) { + throw new AysUserAlreadyPassiveException(); + } + if (!user.isActive()) { throw new AysUserNotActiveException(id); } diff --git a/src/test/java/org/ays/auth/controller/AysUserEndToEndTest.java b/src/test/java/org/ays/auth/controller/AysUserEndToEndTest.java index 2f56f316f..152b4057f 100644 --- a/src/test/java/org/ays/auth/controller/AysUserEndToEndTest.java +++ b/src/test/java/org/ays/auth/controller/AysUserEndToEndTest.java @@ -19,6 +19,8 @@ import org.ays.auth.port.AysUserSavePort; import org.ays.common.model.AysPhoneNumberBuilder; import org.ays.common.model.request.AysPhoneNumberRequestBuilder; +import org.ays.common.model.response.AysErrorResponse; +import org.ays.common.model.response.AysErrorResponseBuilder; import org.ays.common.model.response.AysPageResponse; import org.ays.common.model.response.AysResponse; import org.ays.common.model.response.AysResponseBuilder; @@ -31,8 +33,11 @@ import org.ays.util.UUIDTestUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import java.util.List; import java.util.Optional; @@ -596,4 +601,100 @@ void givenValidId_whenPassivateUser_thenReturnSuccess() throws Exception { Assertions.assertTrue(UUIDTestUtil.isValid(userFromDatabase.get().getUpdatedUser())); } + @Test + void givenAlreadyPassiveUserId_whenTryToPassivate_thenReturnUserAlreadyPassiveError() throws Exception { + + // Initialize + Institution institution = new InstitutionBuilder() + .withId(AysValidTestData.Admin.INSTITUTION_ID) + .build(); + + List roles = roleReadPort.findAllActivesByInstitutionId(institution.getId()); + + AysUser user = userSavePort.save( + new AysUserBuilder() + .withValidValues() + .withoutId() + .withRoles(roles) + .withInstitution(institution) + .withStatus(AysUserStatus.PASSIVE) + .build() + ); + + // Given + String userId = user.getId(); + + // Then + String endpoint = BASE_PATH.concat("/user/").concat(userId).concat("/passivate"); + MockHttpServletRequestBuilder mockHttpServletRequestBuilder = AysMockMvcRequestBuilders + .patch(endpoint, adminToken.getAccessToken()); + + AysErrorResponse mockErrorResponse = AysErrorResponseBuilder.CONFLICT_ERROR; + + aysMockMvc.perform(mockHttpServletRequestBuilder, mockErrorResponse) + .andExpect(AysMockResultMatchersBuilders.status() + .isConflict()) + .andExpect(MockMvcResultMatchers.jsonPath("$.message") + .value("user is already passive!")); + + // Verify + Optional userFromDatabase = userReadPort.findById(userId); + + Assertions.assertTrue(userFromDatabase.isPresent()); + Assertions.assertEquals(userFromDatabase.get().getId(), user.getId()); + Assertions.assertEquals(AysUserStatus.PASSIVE, userFromDatabase.get().getStatus()); + Assertions.assertNull(userFromDatabase.get().getUpdatedUser()); + Assertions.assertNull(userFromDatabase.get().getUpdatedAt()); + } + + @ParameterizedTest + @EnumSource(value = AysUserStatus.class, names = { + "NOT_VERIFIED", + "DELETED" + }) + void givenInactiveUserId_whenTryToPassivate_thenReturnUserNotActiveError(AysUserStatus status) throws Exception { + + // Initialize + Institution institution = new InstitutionBuilder() + .withId(AysValidTestData.Admin.INSTITUTION_ID) + .build(); + + List roles = roleReadPort.findAllActivesByInstitutionId(institution.getId()); + + AysUser user = userSavePort.save( + new AysUserBuilder() + .withValidValues() + .withoutId() + .withRoles(roles) + .withInstitution(institution) + .withStatus(status) + .build() + ); + + // Given + String userId = user.getId(); + + // Then + String endpoint = BASE_PATH.concat("/user/").concat(userId).concat("/passivate"); + MockHttpServletRequestBuilder mockHttpServletRequestBuilder = AysMockMvcRequestBuilders + .patch(endpoint, adminToken.getAccessToken()); + + AysErrorResponse mockErrorResponse = AysErrorResponseBuilder.CONFLICT_ERROR; + + aysMockMvc.perform(mockHttpServletRequestBuilder, mockErrorResponse) + .andExpect(AysMockResultMatchersBuilders.status() + .isConflict()) + .andExpect(MockMvcResultMatchers.jsonPath("$.message") + .value("user is not active! userId:" + userId)); + + // Verify + Optional userFromDatabase = userReadPort.findById(userId); + + Assertions.assertTrue(userFromDatabase.isPresent()); + Assertions.assertEquals(userFromDatabase.get().getId(), user.getId()); + Assertions.assertEquals(status, userFromDatabase.get().getStatus()); + Assertions.assertNull(userFromDatabase.get().getUpdatedUser()); + Assertions.assertNull(userFromDatabase.get().getUpdatedAt()); + } + } diff --git a/src/test/java/org/ays/auth/service/impl/AysAuthServiceImplTest.java b/src/test/java/org/ays/auth/service/impl/AysAuthServiceImplTest.java index 42e6c170d..040539f38 100644 --- a/src/test/java/org/ays/auth/service/impl/AysAuthServiceImplTest.java +++ b/src/test/java/org/ays/auth/service/impl/AysAuthServiceImplTest.java @@ -8,7 +8,7 @@ import org.ays.auth.exception.AysTokenNotValidException; import org.ays.auth.exception.AysUserDoesNotAccessPageException; import org.ays.auth.exception.AysUserIdNotValidException; -import org.ays.auth.exception.AysUserNotActiveException; +import org.ays.auth.exception.AysUserNotActiveAuthException; import org.ays.auth.model.AysIdentity; import org.ays.auth.model.AysPermission; import org.ays.auth.model.AysPermissionBuilder; @@ -284,7 +284,7 @@ void givenValidLoginRequest_whenUserNotActive_thenThrowUserNotActiveException() // Then Assertions.assertThrows( - AysUserNotActiveException.class, + AysUserNotActiveAuthException.class, () -> userAuthService.authenticate(mockLoginRequest) ); @@ -606,7 +606,7 @@ void givenValidRefreshToken_whenUserNotActive_thenThrowUserNotActiveException() // Then Assertions.assertThrows( - AysUserNotActiveException.class, + AysUserNotActiveAuthException.class, () -> userAuthService.refreshAccessToken(mockRefreshToken) ); diff --git a/src/test/java/org/ays/auth/service/impl/AysUserPasswordServiceImplTest.java b/src/test/java/org/ays/auth/service/impl/AysUserPasswordServiceImplTest.java index 720394ae6..cee3974d3 100644 --- a/src/test/java/org/ays/auth/service/impl/AysUserPasswordServiceImplTest.java +++ b/src/test/java/org/ays/auth/service/impl/AysUserPasswordServiceImplTest.java @@ -2,8 +2,8 @@ import org.ays.AysUnitTest; import org.ays.auth.exception.AysEmailAddressNotValidException; -import org.ays.auth.exception.AysUserNotActiveException; import org.ays.auth.exception.AysUserDoesNotAccessPageException; +import org.ays.auth.exception.AysUserNotActiveAuthException; import org.ays.auth.exception.AysUserPasswordCannotChangedException; import org.ays.auth.exception.AysUserPasswordDoesNotExistException; import org.ays.auth.model.AysRole; @@ -206,7 +206,7 @@ void givenValidForgotPasswordRequest_whenUserStatusNotActive_thenThrowUserNotAct // Then Assertions.assertThrows( - AysUserNotActiveException.class, + AysUserNotActiveAuthException.class, () -> userPasswordService.forgotPassword(mockForgotPasswordRequest) ); diff --git a/src/test/java/org/ays/auth/service/impl/AysUserUpdateServiceImplTest.java b/src/test/java/org/ays/auth/service/impl/AysUserUpdateServiceImplTest.java index 6efb70b04..645a33e4c 100644 --- a/src/test/java/org/ays/auth/service/impl/AysUserUpdateServiceImplTest.java +++ b/src/test/java/org/ays/auth/service/impl/AysUserUpdateServiceImplTest.java @@ -5,6 +5,7 @@ import org.ays.auth.exception.AysUserAlreadyDeletedException; import org.ays.auth.exception.AysUserAlreadyExistsByEmailAddressException; import org.ays.auth.exception.AysUserAlreadyExistsByPhoneNumberException; +import org.ays.auth.exception.AysUserAlreadyPassiveException; import org.ays.auth.exception.AysUserIsNotActiveOrPassiveException; import org.ays.auth.exception.AysUserNotActiveException; import org.ays.auth.exception.AysUserNotExistByIdException; @@ -27,6 +28,8 @@ import org.ays.institution.model.InstitutionBuilder; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; @@ -962,7 +965,51 @@ void givenValidUserId_whenUserNotFound_thenThrowAysUserNotExistByIdException() { } @Test - void givenValidId_whenUserIsNotActive_thenThrowAysUserNotActiveException() { + void givenValidId_whenUserIsAlreadyPassive_thenThrowUserAlreadyPassiveException() { + + // Given + String mockId = "b64ef470-6842-400f-ba23-2379c589095c"; + + // When + Institution mockInstitution = new InstitutionBuilder() + .withValidValues() + .build(); + Mockito.when(identity.getInstitutionId()) + .thenReturn(mockInstitution.getId()); + + AysUser mockUser = new AysUserBuilder() + .withValidValues() + .withId(mockId) + .withInstitution(mockInstitution) + .withStatus(AysUserStatus.PASSIVE) + .build(); + + Mockito.when(userReadPort.findById(Mockito.anyString())) + .thenReturn(Optional.of(mockUser)); + + // Then + Assertions.assertThrows( + AysUserAlreadyPassiveException.class, + () -> userUpdateService.passivate(mockId) + ); + + // Verify + Mockito.verify(identity, Mockito.times(1)) + .getInstitutionId(); + + Mockito.verify(userReadPort, Mockito.times(1)) + .findById(Mockito.anyString()); + + Mockito.verify(userSavePort, Mockito.never()) + .save(Mockito.any(AysUser.class)); + } + + @ParameterizedTest + @EnumSource(value = AysUserStatus.class, names = { + "NOT_VERIFIED", + "DELETED" + }) + void givenValidId_whenUserIsNotActive_thenThrowUserNotActiveException(AysUserStatus mockStatus) { // Given String mockId = "bf7cc8d4-eab7-487d-8564-19be0f439b4a"; @@ -978,7 +1025,7 @@ void givenValidId_whenUserIsNotActive_thenThrowAysUserNotActiveException() { .withValidValues() .withId(mockId) .withInstitution(mockInstitution) - .withStatus(AysUserStatus.PASSIVE) // Not active + .withStatus(mockStatus) .build(); Mockito.when(userReadPort.findById(Mockito.anyString()))