diff --git a/apps/user-group-ms/connector-api/src/main/java/it/pagopa/selfcare/user_group/connector/exception/ResourceAlreadyExistsException.java b/apps/user-group-ms/connector-api/src/main/java/it/pagopa/selfcare/user_group/connector/exception/ResourceAlreadyExistsException.java index 9410a1c8..89623f06 100644 --- a/apps/user-group-ms/connector-api/src/main/java/it/pagopa/selfcare/user_group/connector/exception/ResourceAlreadyExistsException.java +++ b/apps/user-group-ms/connector-api/src/main/java/it/pagopa/selfcare/user_group/connector/exception/ResourceAlreadyExistsException.java @@ -5,4 +5,8 @@ public class ResourceAlreadyExistsException extends RuntimeException { public ResourceAlreadyExistsException(String msg, Throwable cause) { super(msg, cause); } + + public ResourceAlreadyExistsException(String msg) { + super(msg); + } } diff --git a/apps/user-group-ms/core/src/main/java/it/pagopa/selfcare/user_group/core/UserGroupServiceImpl.java b/apps/user-group-ms/core/src/main/java/it/pagopa/selfcare/user_group/core/UserGroupServiceImpl.java index d2e8caea..46cfa08c 100644 --- a/apps/user-group-ms/core/src/main/java/it/pagopa/selfcare/user_group/core/UserGroupServiceImpl.java +++ b/apps/user-group-ms/core/src/main/java/it/pagopa/selfcare/user_group/core/UserGroupServiceImpl.java @@ -3,6 +3,7 @@ import it.pagopa.selfcare.commons.base.security.SelfCareUser; import it.pagopa.selfcare.user_group.connector.api.UserGroupConnector; import it.pagopa.selfcare.user_group.connector.api.UserGroupOperations; +import it.pagopa.selfcare.user_group.connector.exception.ResourceAlreadyExistsException; import it.pagopa.selfcare.user_group.connector.exception.ResourceNotFoundException; import it.pagopa.selfcare.user_group.connector.exception.ResourceUpdateException; import it.pagopa.selfcare.user_group.connector.model.UserGroupFilter; @@ -31,6 +32,7 @@ class UserGroupServiceImpl implements UserGroupService { private static final String USER_GROUP_ID_REQUIRED_MESSAGE = "A user group id is required"; private static final String TRYING_TO_MODIFY_SUSPENDED_GROUP = "Trying to modify suspended group"; private static final String MEMBER_ID_REQUIRED = "A member id is required"; + private static final String GROUP_NAME_ALREADY_EXISTS = "A group with the same name already exists in ACTIVE or SUSPENDED state"; private final List allowedSortingParams; @Autowired @@ -49,12 +51,28 @@ public UserGroupOperations createGroup(UserGroupOperations group) { Assert.state(authentication.getPrincipal() instanceof SelfCareUser, "Not SelfCareUser principal"); Assert.notNull(group, "A group is required"); + checkNameUniqueness(group.getId(), group.getName(), group.getProductId(), group.getInstitutionId()); UserGroupOperations insert = groupConnector.insert(group); log.debug("insert = {}", insert); log.trace("createGroup end"); return insert; } + private void checkNameUniqueness(String currentGroupId, String groupName, String productId, String institutionId) { + UserGroupFilter filter = new UserGroupFilter(); + filter.setProductId(productId); + filter.setInstitutionId(institutionId); + filter.setStatus(Arrays.asList(UserGroupStatus.ACTIVE, UserGroupStatus.SUSPENDED)); + + Page existingGroups = groupConnector.findAll(filter, Pageable.unpaged()); + boolean isDuplicate = existingGroups.stream() + .anyMatch(g -> g.getName().equals(groupName) && !g.getId().equals(currentGroupId)); + + if (isDuplicate) { + log.warn("Attempted to create/update group with duplicate name: {}", groupName); + throw new ResourceAlreadyExistsException(GROUP_NAME_ALREADY_EXISTS); + } + } @Override public void addMember(String id, UUID memberId) { @@ -160,6 +178,8 @@ public UserGroupOperations updateGroup(String id, UserGroupOperations group) { if (UserGroupStatus.SUSPENDED.equals(foundGroup.getStatus())) { throw new ResourceUpdateException(TRYING_TO_MODIFY_SUSPENDED_GROUP); } + checkNameUniqueness(id, group.getName(), foundGroup.getProductId(), foundGroup.getInstitutionId()); + foundGroup.setMembers(group.getMembers()); foundGroup.setName(group.getName()); foundGroup.setDescription(group.getDescription()); diff --git a/apps/user-group-ms/core/src/test/java/it/pagopa/selfcare/user_group/core/UserGroupServiceImplTest.java b/apps/user-group-ms/core/src/test/java/it/pagopa/selfcare/user_group/core/UserGroupServiceImplTest.java index e818c986..56624b30 100644 --- a/apps/user-group-ms/core/src/test/java/it/pagopa/selfcare/user_group/core/UserGroupServiceImplTest.java +++ b/apps/user-group-ms/core/src/test/java/it/pagopa/selfcare/user_group/core/UserGroupServiceImplTest.java @@ -5,6 +5,7 @@ import it.pagopa.selfcare.user_group.connector.DummyGroup; import it.pagopa.selfcare.user_group.connector.api.UserGroupConnector; import it.pagopa.selfcare.user_group.connector.api.UserGroupOperations; +import it.pagopa.selfcare.user_group.connector.exception.ResourceAlreadyExistsException; import it.pagopa.selfcare.user_group.connector.exception.ResourceNotFoundException; import it.pagopa.selfcare.user_group.connector.exception.ResourceUpdateException; import it.pagopa.selfcare.user_group.connector.model.UserGroupFilter; @@ -33,8 +34,6 @@ import javax.validation.ValidationException; import java.util.*; -import java.util.stream.Collectors; - import static java.util.UUID.randomUUID; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; @@ -117,10 +116,10 @@ void createGroup_ok() { .build(); TestingAuthenticationToken authenticationToken = new TestingAuthenticationToken(selfCareUser, null); TestSecurityContextHolder.setAuthentication(authenticationToken); - Set members = Set.of(randomUUID(), randomUUID()); UserGroupOperations input = TestUtils.mockInstance(new DummyGroup(), "setId", "setCreateAt", "setModifiedAt"); - input.setId("id"); - input.setMembers(members.stream().map(UUID::toString).collect(Collectors.toSet())); + Page existingGroups = getPage(Collections.emptyList(), Pageable.unpaged(), () -> 0L); + when(groupConnectorMock.findAll(any(), any())) + .thenReturn(existingGroups); when(groupConnectorMock.insert(any(UserGroupOperations.class))) .thenAnswer(invocation -> invocation.getArgument(0, UserGroupOperations.class)); //when @@ -130,6 +129,39 @@ void createGroup_ok() { verify(groupConnectorMock, times(1)) .insert(any(UserGroupOperations.class)); + verify(groupConnectorMock, times(1)) + .findAll(filter.capture(), any()); + verifyNoMoreInteractions(groupConnectorMock); + } + + @Test + void createGroup_conflict() { + //given + SelfCareUser selfCareUser = SelfCareUser.builder("userId") + .email("test@example.com") + .name("name") + .surname("surname") + .build(); + TestingAuthenticationToken authenticationToken = new TestingAuthenticationToken(selfCareUser, null); + TestSecurityContextHolder.setAuthentication(authenticationToken); + UserGroupOperations input = TestUtils.mockInstance(new DummyGroup(), "setId", "setCreateAt", "setModifiedAt"); + input.setName("existingGroupName"); + + UserGroupOperations existingGroup = TestUtils.mockInstance(new DummyGroup(), "setCreateAt", "setModifiedAt"); + existingGroup.setName("existingGroupName"); + Page existingGroups = getPage(Collections.singletonList(existingGroup), Pageable.unpaged(), () -> 1L); + + when(groupConnectorMock.findAll(any(), any())) + .thenReturn(existingGroups); + + //when + Executable executable = () -> groupService.createGroup(input); + + //then + assertThrows(ResourceAlreadyExistsException.class, executable); + + verify(groupConnectorMock, times(1)) + .findAll(filter.capture(), any()); verifyNoMoreInteractions(groupConnectorMock); } @@ -266,28 +298,106 @@ void updateGroup_notExists() { } @Test - void updateGroup_exists() { + void updateGroup_exists_ok() { + //given + String id = "id"; + UserGroupOperations group = TestUtils.mockInstance(new DummyGroup(), "setId"); + UserGroupOperations foundGroup = TestUtils.mockInstance(new DummyGroup()); + Page existingGroups = getPage(Collections.emptyList(), Pageable.unpaged(), () -> 0L); + + when(groupConnectorMock.findAll(any(), any())) + .thenReturn(existingGroups); + when(groupConnectorMock.findById(Mockito.anyString())) + .thenReturn(Optional.of(foundGroup)); + when(groupConnectorMock.save(any())) + .thenAnswer(invocationOnMock -> invocationOnMock.getArgument(0, UserGroupOperations.class)); + //when + UserGroupOperations saved = groupService.updateGroup(id, group); + //then + assertEquals(saved.getDescription(), group.getDescription()); + assertEquals(saved.getMembers(), group.getMembers()); + assertEquals(saved.getName(), group.getName()); + verify(groupConnectorMock, times(1)) + .findById(id); + verify(groupConnectorMock, times(1)) + .findAll(any(), any()); + verify(groupConnectorMock, times(1)) + .save(any()); + verifyNoMoreInteractions(groupConnectorMock); + } + + @Test + void updateGroup_exists_notChangingName() { //given String id = "id"; UserGroupOperations group = TestUtils.mockInstance(new DummyGroup(), "setId"); + group.setName("existingGroupName"); + UserGroupOperations foundGroup = TestUtils.mockInstance(new DummyGroup()); + foundGroup.setId(id); + foundGroup.setName("existingGroupName"); + foundGroup.setStatus(UserGroupStatus.ACTIVE); + + // existing group find the same group without changing the name + Page existingGroups = getPage(Collections.singletonList(foundGroup), Pageable.unpaged(), () -> 1L); + + when(groupConnectorMock.findAll(any(), any())) + .thenReturn(existingGroups); when(groupConnectorMock.findById(Mockito.anyString())) .thenReturn(Optional.of(foundGroup)); when(groupConnectorMock.save(any())) .thenAnswer(invocationOnMock -> invocationOnMock.getArgument(0, UserGroupOperations.class)); + //when UserGroupOperations saved = groupService.updateGroup(id, group); //then assertEquals(saved.getDescription(), group.getDescription()); assertEquals(saved.getMembers(), group.getMembers()); assertEquals(saved.getName(), group.getName()); + verify(groupConnectorMock, times(1)) .findById(id); + verify(groupConnectorMock, times(1)) + .findAll(any(), any()); verify(groupConnectorMock, times(1)) .save(any()); verifyNoMoreInteractions(groupConnectorMock); } + @Test + void updateGroup_exists_conflict() { + //given + String id = "id"; + UserGroupOperations group = TestUtils.mockInstance(new DummyGroup(), "setId"); + group.setName("existingGroupName"); + UserGroupOperations foundGroup = TestUtils.mockInstance(new DummyGroup()); + foundGroup.setStatus(UserGroupStatus.ACTIVE); + + UserGroupOperations existingGroup = TestUtils.mockInstance(new DummyGroup(), "setId"); + existingGroup.setName("existingGroupName"); + existingGroup.setId("differentId"); + Page existingGroups = getPage(Collections.singletonList(existingGroup), Pageable.unpaged(), () -> 1L); + + when(groupConnectorMock.findAll(any(), any())) + .thenReturn(existingGroups); + when(groupConnectorMock.findById(Mockito.anyString())) + .thenReturn(Optional.of(foundGroup)); + when(groupConnectorMock.save(any())) + .thenAnswer(invocationOnMock -> invocationOnMock.getArgument(0, UserGroupOperations.class)); + + //when + Executable executable = () -> groupService.updateGroup(id, group); + + //then + assertThrows(ResourceAlreadyExistsException.class, executable); + + verify(groupConnectorMock, times(1)) + .findById(id); + verify(groupConnectorMock, times(1)) + .findAll(any(), any()); + verifyNoMoreInteractions(groupConnectorMock); + } + @Test void addMember_nullId() { //given