diff --git a/app/src/main/resources/swagger/api-docs.json b/app/src/main/resources/swagger/api-docs.json index b7dd6691d..7f84ab2f5 100644 --- a/app/src/main/resources/swagger/api-docs.json +++ b/app/src/main/resources/swagger/api-docs.json @@ -3323,6 +3323,188 @@ "bearerAuth" : [ "global" ] } ] } + }, + "/v2/institutions" : { + "get" : { + "tags" : [ "user" ], + "summary" : "getInstitutions", + "description" : "Service to get all the institutions related to logged user", + "operationId" : "getInstitutionsUsingGET_1", + "parameters" : [ { + "name" : "authenticated", + "in" : "query", + "required" : false, + "style" : "form", + "schema" : { + "type" : "boolean" + } + }, { + "name" : "authorities[0].authority", + "in" : "query", + "required" : false, + "style" : "form", + "schema" : { + "type" : "string" + } + }, { + "name" : "credentials", + "in" : "query", + "required" : false, + "style" : "form", + "schema" : { + "type" : "object" + } + }, { + "name" : "details", + "in" : "query", + "required" : false, + "style" : "form", + "schema" : { + "type" : "object" + } + }, { + "name" : "principal", + "in" : "query", + "required" : false, + "style" : "form", + "schema" : { + "type" : "object" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/InstitutionBaseResource" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "application/problem+json" : { + "schema" : { + "$ref" : "#/components/schemas/Problem" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "application/problem+json" : { + "schema" : { + "$ref" : "#/components/schemas/Problem" + } + } + } + }, + "404" : { + "description" : "Not Found", + "content" : { + "application/problem+json" : { + "schema" : { + "$ref" : "#/components/schemas/Problem" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "application/problem+json" : { + "schema" : { + "$ref" : "#/components/schemas/Problem" + } + } + } + } + }, + "security" : [ { + "bearerAuth" : [ "global" ] + } ] + } + }, + "/v2/users/{id}" : { + "put" : { + "tags" : [ "user" ], + "summary" : "updateUser", + "description" : "Update previously added user", + "operationId" : "updateUserUsingPUT_1", + "parameters" : [ { + "name" : "id", + "in" : "path", + "description" : "User's unique identifier", + "required" : true, + "style" : "simple", + "schema" : { + "type" : "string", + "format" : "uuid" + } + }, { + "name" : "institutionId", + "in" : "query", + "description" : "Institution's unique internal identifier", + "required" : true, + "style" : "form", + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UpdateUserDto" + } + } + } + }, + "responses" : { + "204" : { + "description" : "No Content" + }, + "400" : { + "description" : "Bad Request", + "content" : { + "application/problem+json" : { + "schema" : { + "$ref" : "#/components/schemas/Problem" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "application/problem+json" : { + "schema" : { + "$ref" : "#/components/schemas/Problem" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "application/problem+json" : { + "schema" : { + "$ref" : "#/components/schemas/Problem" + } + } + } + } + }, + "security" : [ { + "bearerAuth" : [ "global" ] + } ] + } } }, "components" : { diff --git a/app/src/test/java/it/pagopa/selfcare/dashboard/web/config/SwaggerConfigTest.java b/app/src/test/java/it/pagopa/selfcare/dashboard/web/config/SwaggerConfigTest.java index a067ecac9..c086979d5 100644 --- a/app/src/test/java/it/pagopa/selfcare/dashboard/web/config/SwaggerConfigTest.java +++ b/app/src/test/java/it/pagopa/selfcare/dashboard/web/config/SwaggerConfigTest.java @@ -60,6 +60,9 @@ class SwaggerConfigTest { @MockBean private UserService userServiceMock; + @MockBean + private UserV2Service userV2ServiceMock; + @MockBean private BrokerService brokerService; diff --git a/connector/rest/src/main/java/it/pagopa/selfcare/dashboard/connector/rest/UserConnectorImpl.java b/connector/rest/src/main/java/it/pagopa/selfcare/dashboard/connector/rest/UserConnectorImpl.java index a2645959f..9120d1c7d 100644 --- a/connector/rest/src/main/java/it/pagopa/selfcare/dashboard/connector/rest/UserConnectorImpl.java +++ b/connector/rest/src/main/java/it/pagopa/selfcare/dashboard/connector/rest/UserConnectorImpl.java @@ -19,7 +19,6 @@ @Slf4j @Service -@ConditionalOnProperty(value = "dashboard.user.client.api-version", havingValue = "v2") @RequiredArgsConstructor public class UserConnectorImpl implements UserApiConnector { diff --git a/core/src/main/java/it/pagopa/selfcare/dashboard/core/UserV2Service.java b/core/src/main/java/it/pagopa/selfcare/dashboard/core/UserV2Service.java new file mode 100644 index 000000000..27d1ac880 --- /dev/null +++ b/core/src/main/java/it/pagopa/selfcare/dashboard/core/UserV2Service.java @@ -0,0 +1,16 @@ +package it.pagopa.selfcare.dashboard.core; + +import it.pagopa.selfcare.dashboard.connector.model.institution.InstitutionInfo; +import it.pagopa.selfcare.dashboard.connector.model.user.*; + +import java.util.Collection; +import java.util.UUID; + +public interface UserV2Service { + + void updateUser(UUID id, String institutionId, MutableUserFieldsDto userDto); + + Collection getInstitutions(String userId); + + +} diff --git a/core/src/main/java/it/pagopa/selfcare/dashboard/core/UserV2ServiceImpl.java b/core/src/main/java/it/pagopa/selfcare/dashboard/core/UserV2ServiceImpl.java new file mode 100644 index 000000000..4519a83de --- /dev/null +++ b/core/src/main/java/it/pagopa/selfcare/dashboard/core/UserV2ServiceImpl.java @@ -0,0 +1,56 @@ +package it.pagopa.selfcare.dashboard.core; + +import it.pagopa.selfcare.commons.base.logging.LogUtils; +import it.pagopa.selfcare.dashboard.connector.api.MsCoreConnector; +import it.pagopa.selfcare.dashboard.connector.api.UserApiConnector; +import it.pagopa.selfcare.dashboard.connector.api.UserRegistryConnector; +import it.pagopa.selfcare.dashboard.connector.exception.ResourceNotFoundException; +import it.pagopa.selfcare.dashboard.connector.model.institution.Institution; +import it.pagopa.selfcare.dashboard.connector.model.institution.InstitutionInfo; +import it.pagopa.selfcare.dashboard.connector.model.user.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import static it.pagopa.selfcare.dashboard.connector.model.user.User.Fields.*; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserV2ServiceImpl implements UserV2Service { + + private final MsCoreConnector msCoreConnector; + private final UserApiConnector userApiConnector; + + @Override + public void updateUser(UUID id, String institutionId, MutableUserFieldsDto userDto) { + log.trace("updateUser start"); + log.debug(LogUtils.CONFIDENTIAL_MARKER, "updateUser id = {}, institutionId = {}, userDto = {}", id, institutionId, userDto); + Assert.notNull(id, "UUID is required"); + Assert.hasText(institutionId, "An institutionId is required"); + Assert.notNull(userDto, "A userDto is required"); + Institution institution = msCoreConnector.getInstitution(institutionId); + if (institution == null) { + throw new ResourceNotFoundException("There are no institution for given institutionId"); + } + userApiConnector.updateUser(id.toString(), institutionId, userDto); + log.trace("updateUser end"); + } + + @Override + public Collection getInstitutions(String userId) { + log.trace("getInstitutions start"); + Collection result = userApiConnector.getUserProducts(userId); + log.debug(LogUtils.CONFIDENTIAL_MARKER, "getInstitutions result = {}", result); + log.trace("getInstitutions end"); + return result; + } + +} diff --git a/core/src/test/java/it/pagopa/selfcare/dashboard/core/UserV2ServiceImplTest.java b/core/src/test/java/it/pagopa/selfcare/dashboard/core/UserV2ServiceImplTest.java new file mode 100644 index 000000000..8e53a7735 --- /dev/null +++ b/core/src/test/java/it/pagopa/selfcare/dashboard/core/UserV2ServiceImplTest.java @@ -0,0 +1,138 @@ +package it.pagopa.selfcare.dashboard.core; + +import it.pagopa.selfcare.dashboard.connector.api.MsCoreConnector; +import it.pagopa.selfcare.dashboard.connector.api.UserApiConnector; +import it.pagopa.selfcare.dashboard.connector.api.UserRegistryConnector; +import it.pagopa.selfcare.dashboard.connector.exception.ResourceNotFoundException; +import it.pagopa.selfcare.dashboard.connector.model.institution.Institution; +import it.pagopa.selfcare.dashboard.connector.model.institution.InstitutionInfo; +import it.pagopa.selfcare.dashboard.connector.model.user.MutableUserFieldsDto; +import it.pagopa.selfcare.dashboard.connector.model.user.WorkContact; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.function.Executable; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static it.pagopa.selfcare.commons.utils.TestUtils.mockInstance; +import static java.util.UUID.randomUUID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.verifyNoInteractions; + +@ExtendWith({MockitoExtension.class}) +class UserV2ServiceImplTest { + + @Mock + private UserRegistryConnector userConnectorMock; + + @InjectMocks + private UserV2ServiceImpl userService; + + @Mock + private MsCoreConnector msCoreConnectorMock; + + @Mock + private UserApiConnector userApiConnector; + + + @Test + void updateUser() { + //given + String institutionId = "institutionId"; + UUID id = randomUUID(); + MutableUserFieldsDto user = mockInstance(new MutableUserFieldsDto(), "setWorkContacts"); + WorkContact workContact = mockInstance(new WorkContact()); + user.setWorkContacts(Map.of(institutionId, workContact)); + Institution institutionMock = mockInstance(new Institution()); + when(msCoreConnectorMock.getInstitution(Mockito.anyString())) + .thenReturn(institutionMock); + doNothing().when(userApiConnector).updateUser(id.toString(), institutionId, user); + //when + Executable executable = () -> userService.updateUser(id, institutionId, user); + //then + assertDoesNotThrow(executable); + verify(msCoreConnectorMock, times(1)) + .getInstitution(institutionId); + verifyNoMoreInteractions(userConnectorMock, msCoreConnectorMock); + } + + @Test + void updateUser_nullInstitution() { + //given + String institutionId = "institutionId"; + UUID id = randomUUID(); + MutableUserFieldsDto user = mockInstance(new MutableUserFieldsDto(), "setWorkContacts"); + //when + Executable executable = () -> userService.updateUser(id, institutionId, user); + //then + ResourceNotFoundException e = assertThrows(ResourceNotFoundException.class, executable); + assertEquals("There are no institution for given institutionId", e.getMessage()); + verifyNoInteractions(userConnectorMock); + } + + @Test + void updateUser_nullInstitutionId() { + //given + String institutionId = null; + UUID id = randomUUID(); + MutableUserFieldsDto user = mockInstance(new MutableUserFieldsDto(), "setWorkContacts"); + //when + Executable executable = () -> userService.updateUser(id, institutionId, user); + //then + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, executable); + assertEquals("An institutionId is required", e.getMessage()); + verifyNoInteractions(userConnectorMock, msCoreConnectorMock); + } + + @Test + void updateUser_nullUUID() { + //given + String institutionId = "institutionId"; + UUID id = null; + MutableUserFieldsDto user = mockInstance(new MutableUserFieldsDto(), "setWorkContacts"); + //when + Executable executable = () -> userService.updateUser(id, institutionId, user); + //then + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, executable); + assertEquals("UUID is required", e.getMessage()); + verifyNoInteractions(userConnectorMock, msCoreConnectorMock); + } + + @Test + void updateUser_nullDto() { + //given + String institutionId = "institutionId"; + UUID id = randomUUID(); + //when + Executable executable = () -> userService.updateUser(id, institutionId, null); + //then + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, executable); + assertEquals("A userDto is required", e.getMessage()); + verifyNoInteractions(userConnectorMock, msCoreConnectorMock); + } + + @Test + void getInstitutions() { + // given + String userId = "userId"; + InstitutionInfo expectedInstitutionInfo = new InstitutionInfo(); + + when(userApiConnector.getUserProducts(userId)).thenReturn(List.of(expectedInstitutionInfo)); + // when + Collection institutions = userService.getInstitutions(userId); + + assertNotNull(institutions); + assertEquals(1, institutions.size()); + assertSame(expectedInstitutionInfo, institutions.iterator().next()); + verifyNoMoreInteractions(msCoreConnectorMock); + } + +} diff --git a/web/src/main/java/it/pagopa/selfcare/dashboard/web/controller/UserV2Controller.java b/web/src/main/java/it/pagopa/selfcare/dashboard/web/controller/UserV2Controller.java new file mode 100644 index 000000000..72a51856d --- /dev/null +++ b/web/src/main/java/it/pagopa/selfcare/dashboard/web/controller/UserV2Controller.java @@ -0,0 +1,71 @@ +package it.pagopa.selfcare.dashboard.web.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import it.pagopa.selfcare.commons.base.logging.LogUtils; +import it.pagopa.selfcare.commons.base.security.SelfCareUser; +import it.pagopa.selfcare.dashboard.connector.model.institution.InstitutionInfo; +import it.pagopa.selfcare.dashboard.core.UserV2Service; +import it.pagopa.selfcare.dashboard.web.InstitutionBaseResource; +import it.pagopa.selfcare.dashboard.web.model.UpdateUserDto; +import it.pagopa.selfcare.dashboard.web.model.mapper.InstitutionResourceMapper; +import it.pagopa.selfcare.dashboard.web.model.mapper.UserMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +@Slf4j +@RestController +@Api(tags = "user") +@RequestMapping(value = "/v2", produces = MediaType.APPLICATION_JSON_VALUE) +@RequiredArgsConstructor +public class UserV2Controller { + + private final UserV2Service userService; + private final InstitutionResourceMapper institutionResourceMapper; + + @PutMapping(value = "/users/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + @ApiOperation(value = "", notes = "${swagger.dashboard.user.api.updateUserById}") + public void updateUser(@ApiParam("${swagger.dashboard.user.model.id}") + @PathVariable("id") + UUID id, + @ApiParam("${swagger.dashboard.institutions.model.id}") + @RequestParam(value = "institutionId") + String institutionId, + @RequestBody + @Valid + UpdateUserDto updateUserDto) { + log.trace("updateUser start"); + log.debug(LogUtils.CONFIDENTIAL_MARKER, "id = {}, institutionId = {}, userDto = {}", id, institutionId, updateUserDto); + userService.updateUser(id, institutionId, UserMapper.fromUpdateUser(updateUserDto, institutionId)); + log.trace("updateUser end"); + } + + @GetMapping("/institutions") + @ResponseStatus(HttpStatus.OK) + @ApiOperation(value = "", notes = "${swagger.dashboard.institutions.api.getInstitutions}") + public List getInstitutions(Authentication authentication) { + + log.trace("getInstitutions start"); + String userId = ((SelfCareUser) authentication.getPrincipal()).getId(); + Collection institutions = userService.getInstitutions(userId); + + List result = institutions.stream() + .map(institutionResourceMapper::toResource) + .toList(); + log.debug(LogUtils.CONFIDENTIAL_MARKER, "getInstitutions result = {}", result); + log.trace("getInstitutions end"); + + return result; + } +} diff --git a/web/src/test/java/it/pagopa/selfcare/dashboard/web/controller/UserV2ControllerTest.java b/web/src/test/java/it/pagopa/selfcare/dashboard/web/controller/UserV2ControllerTest.java new file mode 100644 index 000000000..886eec1f5 --- /dev/null +++ b/web/src/test/java/it/pagopa/selfcare/dashboard/web/controller/UserV2ControllerTest.java @@ -0,0 +1,133 @@ +package it.pagopa.selfcare.dashboard.web.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import it.pagopa.selfcare.commons.base.security.SelfCareUser; +import it.pagopa.selfcare.commons.utils.TestUtils; +import it.pagopa.selfcare.dashboard.connector.model.institution.GeographicTaxonomy; +import it.pagopa.selfcare.dashboard.connector.model.institution.InstitutionInfo; +import it.pagopa.selfcare.dashboard.connector.model.user.Certification; +import it.pagopa.selfcare.dashboard.connector.model.user.MutableUserFieldsDto; +import it.pagopa.selfcare.dashboard.connector.model.user.User; +import it.pagopa.selfcare.dashboard.connector.model.user.WorkContact; +import it.pagopa.selfcare.dashboard.core.UserService; +import it.pagopa.selfcare.dashboard.core.UserV2Service; +import it.pagopa.selfcare.dashboard.web.InstitutionBaseResource; +import it.pagopa.selfcare.dashboard.web.config.WebTestConfig; +import it.pagopa.selfcare.dashboard.web.model.GET_INSTITUTION_MODE; +import it.pagopa.selfcare.dashboard.web.model.InstitutionResource; +import it.pagopa.selfcare.dashboard.web.model.mapper.InstitutionResourceMapper; +import it.pagopa.selfcare.dashboard.web.model.mapper.InstitutionResourceMapperImpl; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.*; + +import static it.pagopa.selfcare.commons.utils.TestUtils.mockInstance; +import static org.hamcrest.Matchers.emptyString; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(value = {UserV2Controller.class}, excludeAutoConfiguration = SecurityAutoConfiguration.class) +@ContextConfiguration(classes = {UserV2Controller.class, InstitutionResourceMapperImpl.class, WebTestConfig.class}) +class UserV2ControllerTest { + + @Autowired + protected MockMvc mvc; + + @MockBean + private UserV2Service userServiceMock; + + + @Autowired + protected ObjectMapper objectMapper; + + private static final String BASE_URL = "/v2"; + + private static final User USER_RESOURCE; + + static { + USER_RESOURCE = TestUtils.mockInstance(new User()); + USER_RESOURCE.setId(UUID.randomUUID().toString()); + Map workContacts = new HashMap<>(); + WorkContact workContact = TestUtils.mockInstance(new WorkContact()); + workContact.getEmail().setCertification(Certification.SPID); + workContacts.put("institutionId", workContact); + USER_RESOURCE.setWorkContacts(workContacts); + } + + + @Test + void updateUser(@Value("classpath:stubs/updateUserDto.json") Resource updateUserDto) throws Exception { + //given + UUID id = UUID.randomUUID(); + String institutionId = "institutionId"; + //when + mvc.perform(MockMvcRequestBuilders + .put(BASE_URL + "/users/{id}", id) + .queryParam("institutionId", institutionId) + .content(updateUserDto.getInputStream().readAllBytes()) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNoContent()) + .andExpect(content().string(emptyString())); + //then + verify(userServiceMock, times(1)) + .updateUser(eq(id), eq(institutionId), any(MutableUserFieldsDto.class)); + Mockito.verifyNoMoreInteractions(userServiceMock); + } + + @Test + void getInstitutions_institutionInfoNotNull() throws Exception { + // given + String userId = "userId"; + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(SelfCareUser.builder(userId).build()); + + InstitutionInfo expectedInstitution = mockInstance(new InstitutionInfo()); + expectedInstitution.setGeographicTaxonomies(List.of(mockInstance(new GeographicTaxonomy()))); + List expectedInstitutionInfos = new ArrayList<>(); + expectedInstitutionInfos.add(expectedInstitution); + when(userServiceMock.getInstitutions(userId)).thenReturn(expectedInstitutionInfos); + // when + MvcResult result = mvc.perform(MockMvcRequestBuilders + .get(BASE_URL + "/institutions") + .principal(authentication) + .contentType(APPLICATION_JSON_VALUE) + .accept(APPLICATION_JSON_VALUE)) + .andExpect(status().is2xxSuccessful()) + .andReturn(); + // then + List resources = objectMapper.readValue(result.getResponse().getContentAsString(), + new TypeReference<>() { + }); + + assertNotNull(resources); + assertFalse(resources.isEmpty()); + assertEquals(resources.get(0).getStatus(), expectedInstitution.getStatus().name()); + assertNotNull(resources.get(0).getUserRole()); + verify(userServiceMock, times(1)) + .getInstitutions(userId); + verifyNoMoreInteractions(userServiceMock); + } + + +}