diff --git a/src/main/java/org/ays/auth/model/request/AysRoleUpdateRequest.java b/src/main/java/org/ays/auth/model/request/AysRoleUpdateRequest.java index ff28ea718..80a533bdc 100644 --- a/src/main/java/org/ays/auth/model/request/AysRoleUpdateRequest.java +++ b/src/main/java/org/ays/auth/model/request/AysRoleUpdateRequest.java @@ -5,21 +5,25 @@ import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.Setter; -import org.ays.common.util.validation.NoSpecialCharacters; +import org.ays.common.util.validation.Name; import org.hibernate.validator.constraints.UUID; import java.util.Set; +/** + * Request object for updating a role. + * This class is used to encapsulate the data needed to update a role, including the role name and permission IDs. + */ @Getter @Setter public class AysRoleUpdateRequest { + @Name @NotBlank @Size(min = 2, max = 255) - @NoSpecialCharacters private String name; @NotEmpty - private Set<@UUID String> permissionIds; + private Set<@NotBlank @UUID String> permissionIds; } diff --git a/src/main/java/org/ays/auth/service/impl/AysRoleUpdateServiceImpl.java b/src/main/java/org/ays/auth/service/impl/AysRoleUpdateServiceImpl.java index 4e96c9c74..cc92faa45 100644 --- a/src/main/java/org/ays/auth/service/impl/AysRoleUpdateServiceImpl.java +++ b/src/main/java/org/ays/auth/service/impl/AysRoleUpdateServiceImpl.java @@ -10,13 +10,13 @@ import org.ays.auth.port.AysRoleReadPort; import org.ays.auth.port.AysRoleSavePort; import org.ays.auth.service.AysRoleUpdateService; +import org.ays.auth.util.exception.AysInvalidRoleStatusException; import org.ays.auth.util.exception.AysPermissionNotExistException; import org.ays.auth.util.exception.AysRoleAlreadyDeletedException; import org.ays.auth.util.exception.AysRoleAlreadyExistsByNameException; import org.ays.auth.util.exception.AysRoleAssignedToUserException; import org.ays.auth.util.exception.AysRoleNotExistByIdException; import org.ays.auth.util.exception.AysUserNotSuperAdminException; -import org.ays.auth.util.exception.AysInvalidRoleStatusException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -42,19 +42,24 @@ class AysRoleUpdateServiceImpl implements AysRoleUpdateService { /** * Updates an existing role identified by its ID. - * Performs checks to ensure the role name is unique and validates the existence of provided permissions. + *

+ * This method performs checks to ensure the role name is unique and validates the existence of provided permissions. + * It also verifies that the role belongs to the same institution as the current user's institution. + *

* * @param id The ID of the role to update. * @param updateRequest The request object containing updated data for the role. * @throws AysRoleAlreadyExistsByNameException if a role with the same name already exists, excluding the current role ID. * @throws AysPermissionNotExistException if any of the permission IDs provided do not exist. * @throws AysUserNotSuperAdminException if the current user does not have super admin privileges required for assigning super permissions. + * @throws AysRoleNotExistByIdException if the role with the given ID does not exist or does not belong to the current user's institution. */ @Override public void update(final String id, final AysRoleUpdateRequest updateRequest) { final AysRole role = roleReadPort.findById(id) + .filter(roleFromDatabase -> identity.getInstitutionId().equals(roleFromDatabase.getInstitution().getId())) .orElseThrow(() -> new AysRoleNotExistByIdException(id)); this.checkExistingRoleNameByWithoutId(id, updateRequest.getName()); @@ -77,7 +82,7 @@ public void update(final String id, *

* * @param id The ID of the role to activate. - * @throws AysRoleNotExistByIdException if a role with the given ID does not exist. + * @throws AysRoleNotExistByIdException if a role with the given ID does not exist. * @throws AysInvalidRoleStatusException if the role's current status is not {@link AysRoleStatus#PASSIVE}. */ @Override diff --git a/src/test/java/org/ays/auth/controller/AysRoleControllerTest.java b/src/test/java/org/ays/auth/controller/AysRoleControllerTest.java index f35b3ab3f..3ee69a4ef 100644 --- a/src/test/java/org/ays/auth/controller/AysRoleControllerTest.java +++ b/src/test/java/org/ays/auth/controller/AysRoleControllerTest.java @@ -33,11 +33,13 @@ import org.ays.util.AysMockResultMatchersBuilders; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -345,11 +347,11 @@ void givenValidRoleCreateRequest_whenUserUnauthorized_thenReturnAccessDeniedExce "493268349068342", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec odio nec urna tincidunt fermentum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec odio nec urna tincidunt fermentum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec odio nec urna tincidunt fermentum." }) - void givenInvalidRoleCreateRequest_whenNameIsNotValid_thenReturnValidationError(String name) throws Exception { + void givenInvalidRoleCreateRequest_whenNameIsNotValid_thenReturnValidationError(String invalidName) throws Exception { // Given AysRoleCreateRequest mockCreateRequest = new AysRoleCreateRequestBuilder() .withValidValues() - .withName(name) + .withName(invalidName) .build(); // Then @@ -427,11 +429,11 @@ void givenInvalidRoleCreateRequest_whenPermissionIdsAreEmpty_thenReturnValidatio "", "55aed4c4facb4b66bdb5-309eaaef4453" }) - void givenInvalidRoleCreateRequest_whenPermissionIdIsNotValid_thenReturnValidationError(String permissionId) throws Exception { + void givenInvalidRoleCreateRequest_whenPermissionIdIsNotValid_thenReturnValidationError(String invalidPermissionId) throws Exception { // Given AysRoleCreateRequest mockCreateRequest = new AysRoleCreateRequestBuilder() .withValidValues() - .withPermissionIds(Set.of(permissionId)) + .withPermissionIds(Set.of(invalidPermissionId)) .build(); // Then @@ -544,17 +546,21 @@ void givenInvalidIdAndValidRoleUpdateRequest_whenIdNotValid_thenReturnValidation @ValueSource(strings = { "", "A", + " Role", + "Role ", + "123Role", + ".Role", "% fsdh ", "493268349068342", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec odio nec urna tincidunt fermentum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec odio nec urna tincidunt fermentum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec odio nec urna tincidunt fermentum." }) - void givenValidIdAndInvalidRoleUpdateRequest_whenNameIsNotValid_thenReturnValidationError(String name) throws Exception { + void givenValidIdAndInvalidRoleUpdateRequest_whenNameIsNotValid_thenReturnValidationError(String invalidName) throws Exception { // Given String mockId = AysRandomUtil.generateUUID(); AysRoleUpdateRequest mockUpdateRequest = new AysRoleUpdateRequestBuilder() .withValidValues() - .withName(name) + .withName(invalidName) .build(); // Then @@ -632,17 +638,21 @@ void givenValidIdAndInvalidRoleUpdateRequest_whenPermissionIdsAreEmpty_thenRetur } @ParameterizedTest + @NullSource @ValueSource(strings = { "", "55aed4c4facb4b66bdb5-309eaaef4453" }) - void givenValidIdAndInvalidRoleUpdateRequest_whenPermissionIdIsNotValid_thenReturnValidationError(String permissionId) throws Exception { + void givenValidIdAndInvalidRoleUpdateRequest_whenPermissionIdIsNotValid_thenReturnValidationError(String invalidPermissionId) throws Exception { // Given String mockId = AysRandomUtil.generateUUID(); + + Set mockPermissionIds = new HashSet<>(); + mockPermissionIds.add(invalidPermissionId); AysRoleUpdateRequest mockUpdateRequest = new AysRoleUpdateRequestBuilder() .withValidValues() - .withPermissionIds(Set.of(permissionId)) + .withPermissionIds(mockPermissionIds) .build(); // Then diff --git a/src/test/java/org/ays/auth/controller/AysRoleEndToEndTest.java b/src/test/java/org/ays/auth/controller/AysRoleEndToEndTest.java index 823ffc77a..1b16875d7 100644 --- a/src/test/java/org/ays/auth/controller/AysRoleEndToEndTest.java +++ b/src/test/java/org/ays/auth/controller/AysRoleEndToEndTest.java @@ -380,7 +380,7 @@ void givenValidIdAndRoleUpdateRequest_whenSuperRoleUpdated_thenReturnSuccess() t .withoutId() .withName("Admin Role 1") .withPermissions(permissions) - .withInstitution(new InstitutionBuilder().withId(AysValidTestData.Admin.INSTITUTION_ID).build()) + .withInstitution(new InstitutionBuilder().withId(AysValidTestData.SuperAdmin.INSTITUTION_ID).build()) .build() ); diff --git a/src/test/java/org/ays/auth/model/AysRoleBuilder.java b/src/test/java/org/ays/auth/model/AysRoleBuilder.java index 86a741153..73b4cb867 100644 --- a/src/test/java/org/ays/auth/model/AysRoleBuilder.java +++ b/src/test/java/org/ays/auth/model/AysRoleBuilder.java @@ -5,6 +5,7 @@ import org.ays.common.model.TestDataBuilder; import org.ays.common.util.AysRandomUtil; import org.ays.institution.model.Institution; +import org.ays.institution.model.InstitutionBuilder; import java.util.List; @@ -23,7 +24,8 @@ public AysRoleBuilder withValidValues() { .withId(AysRandomUtil.generateUUID()) .withName("admin") .withPermissions(permissions) - .withStatus(AysRoleStatus.ACTIVE); + .withStatus(AysRoleStatus.ACTIVE) + .withInstitution(new InstitutionBuilder().withValidValues().build()); } public AysRoleBuilder withId(String id) { diff --git a/src/test/java/org/ays/auth/service/impl/AysRoleUpdateServiceImplTest.java b/src/test/java/org/ays/auth/service/impl/AysRoleUpdateServiceImplTest.java index b31fb373a..2419a0e50 100644 --- a/src/test/java/org/ays/auth/service/impl/AysRoleUpdateServiceImplTest.java +++ b/src/test/java/org/ays/auth/service/impl/AysRoleUpdateServiceImplTest.java @@ -12,13 +12,13 @@ import org.ays.auth.port.AysPermissionReadPort; import org.ays.auth.port.AysRoleReadPort; import org.ays.auth.port.AysRoleSavePort; +import org.ays.auth.util.exception.AysInvalidRoleStatusException; import org.ays.auth.util.exception.AysPermissionNotExistException; import org.ays.auth.util.exception.AysRoleAlreadyDeletedException; import org.ays.auth.util.exception.AysRoleAlreadyExistsByNameException; import org.ays.auth.util.exception.AysRoleAssignedToUserException; import org.ays.auth.util.exception.AysRoleNotExistByIdException; import org.ays.auth.util.exception.AysUserNotSuperAdminException; -import org.ays.auth.util.exception.AysInvalidRoleStatusException; import org.ays.common.util.AysRandomUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.UUID; class AysRoleUpdateServiceImplTest extends AysUnitTest { @@ -49,6 +50,7 @@ class AysRoleUpdateServiceImplTest extends AysUnitTest { @Mock private AysIdentity identity; + @Test void givenIdAndRoleUpdateRequest_whenValuesValid_thenUpdateRoleWithSuperPermissions() { // Given @@ -65,6 +67,9 @@ void givenIdAndRoleUpdateRequest_whenValuesValid_thenUpdateRoleWithSuperPermissi Mockito.when(roleReadPort.findById(Mockito.anyString())) .thenReturn(Optional.of(mockRole)); + Mockito.when(identity.getInstitutionId()) + .thenReturn(mockRole.getInstitution().getId()); + Mockito.when(roleReadPort.findByName(Mockito.anyString())) .thenReturn(Optional.empty()); @@ -93,6 +98,9 @@ void givenIdAndRoleUpdateRequest_whenValuesValid_thenUpdateRoleWithSuperPermissi Mockito.verify(roleReadPort, Mockito.times(1)) .findById(Mockito.anyString()); + Mockito.verify(identity, Mockito.times(1)) + .getInstitutionId(); + Mockito.verify(roleReadPort, Mockito.times(1)) .findByName(Mockito.anyString()); @@ -125,6 +133,9 @@ void givenIdAndRoleUpdateRequest_whenValuesValid_thenUpdateRole() { Mockito.when(roleReadPort.findById(Mockito.anyString())) .thenReturn(Optional.of(mockRole)); + Mockito.when(identity.getInstitutionId()) + .thenReturn(mockRole.getInstitution().getId()); + Mockito.when(roleReadPort.findByName(Mockito.anyString())) .thenReturn(Optional.empty()); @@ -143,9 +154,6 @@ void givenIdAndRoleUpdateRequest_whenValuesValid_thenUpdateRole() { Mockito.when(identity.isSuperAdmin()) .thenReturn(false); - Mockito.when(identity.getInstitutionId()) - .thenReturn(AysRandomUtil.generateUUID()); - Mockito.when(roleSavePort.save(Mockito.any(AysRole.class))) .thenReturn(Mockito.mock(AysRole.class)); @@ -156,6 +164,9 @@ void givenIdAndRoleUpdateRequest_whenValuesValid_thenUpdateRole() { Mockito.verify(roleReadPort, Mockito.times(1)) .findById(Mockito.anyString()); + Mockito.verify(identity, Mockito.times(1)) + .getInstitutionId(); + Mockito.verify(roleReadPort, Mockito.times(1)) .findByName(Mockito.anyString()); @@ -194,6 +205,57 @@ void givenValidIdAndRoleUpdateRequest_whenRoleNotFound_thenThrowAysRoleNotExistB Mockito.verify(roleReadPort, Mockito.times(1)) .findById(Mockito.anyString()); + Mockito.verify(identity, Mockito.never()) + .getInstitutionId(); + + Mockito.verify(roleReadPort, Mockito.never()) + .findByName(Mockito.anyString()); + + Mockito.verify(permissionReadPort, Mockito.never()) + .findAllByIds(Mockito.anySet()); + + Mockito.verify(identity, Mockito.never()) + .isSuperAdmin(); + + Mockito.verify(identity, Mockito.never()) + .getUserId(); + + Mockito.verify(roleSavePort, Mockito.never()) + .save(Mockito.any(AysRole.class)); + } + + @Test + void givenValidIdAndRoleUpdateRequest_whenRoleNotMatchedWithInstitution_thenThrowAysRoleNotExistByIdException() { + // Given + String mockId = AysRandomUtil.generateUUID(); + AysRoleUpdateRequest mockUpdateRequest = new AysRoleUpdateRequestBuilder() + .withValidValues() + .build(); + + // When + AysRole mockRole = new AysRoleBuilder() + .withValidValues() + .withId(mockId) + .build(); + Mockito.when(roleReadPort.findById(Mockito.anyString())) + .thenReturn(Optional.of(mockRole)); + + Mockito.when(identity.getInstitutionId()) + .thenReturn(UUID.randomUUID().toString()); + + // Then + Assertions.assertThrows( + AysRoleNotExistByIdException.class, + () -> roleUpdateService.update(mockId, mockUpdateRequest) + ); + + // Verify + Mockito.verify(roleReadPort, Mockito.times(1)) + .findById(Mockito.anyString()); + + Mockito.verify(identity, Mockito.times(1)) + .getInstitutionId(); + Mockito.verify(roleReadPort, Mockito.never()) .findByName(Mockito.anyString()); @@ -226,6 +288,9 @@ void givenValidIdAndRoleUpdateRequest_whenNameAlreadyExist_thenThrowAysRoleAlrea Mockito.when(roleReadPort.findById(Mockito.anyString())) .thenReturn(Optional.of(mockRole)); + Mockito.when(identity.getInstitutionId()) + .thenReturn(mockRole.getInstitution().getId()); + Mockito.when(roleReadPort.findByName(Mockito.anyString())) .thenReturn(Optional.of(Mockito.mock(AysRole.class))); @@ -239,6 +304,9 @@ void givenValidIdAndRoleUpdateRequest_whenNameAlreadyExist_thenThrowAysRoleAlrea Mockito.verify(roleReadPort, Mockito.times(1)) .findById(Mockito.anyString()); + Mockito.verify(identity, Mockito.times(1)) + .getInstitutionId(); + Mockito.verify(roleReadPort, Mockito.times(1)) .findByName(Mockito.anyString()); @@ -271,6 +339,9 @@ void givenValidIdAndRoleUpdateRequest_whenRequestHasSuperPermissionsAndUserIsNot Mockito.when(roleReadPort.findById(Mockito.anyString())) .thenReturn(Optional.of(mockRole)); + Mockito.when(identity.getInstitutionId()) + .thenReturn(mockRole.getInstitution().getId()); + Mockito.when(roleReadPort.findByName(Mockito.anyString())) .thenReturn(Optional.empty()); @@ -302,6 +373,9 @@ void givenValidIdAndRoleUpdateRequest_whenRequestHasSuperPermissionsAndUserIsNot Mockito.verify(roleReadPort, Mockito.times(1)) .findById(Mockito.anyString()); + Mockito.verify(identity, Mockito.times(1)) + .getInstitutionId(); + Mockito.verify(roleReadPort, Mockito.times(1)) .findByName(Mockito.anyString()); @@ -335,6 +409,9 @@ void givenValidIdAndRoleUpdateRequest_whenPermissionsNotExists_thenThrowAysPermi Mockito.when(roleReadPort.findById(Mockito.anyString())) .thenReturn(Optional.of(mockRole)); + Mockito.when(identity.getInstitutionId()) + .thenReturn(mockRole.getInstitution().getId()); + Mockito.when(roleReadPort.findByName(Mockito.anyString())) .thenReturn(Optional.empty()); @@ -351,6 +428,9 @@ void givenValidIdAndRoleUpdateRequest_whenPermissionsNotExists_thenThrowAysPermi Mockito.verify(roleReadPort, Mockito.times(1)) .findById(Mockito.anyString()); + Mockito.verify(identity, Mockito.times(1)) + .getInstitutionId(); + Mockito.verify(roleReadPort, Mockito.times(1)) .findByName(Mockito.anyString());