diff --git a/goofy-server/pom.xml b/goofy-server/pom.xml index f961b8017a703c638de6722234134116ab0249a3..ed0add92cdc0104dbec37efe9d8078092efae85d 100644 --- a/goofy-server/pom.xml +++ b/goofy-server/pom.xml @@ -118,6 +118,10 @@ <groupId>de.itvsh.kop.common</groupId> <artifactId>kop-common-pdf</artifactId> </dependency> + <dependency> + <groupId>de.itvsh.kop.user</groupId> + <artifactId>user-manager-interface</artifactId> + </dependency> <!-- tools --> <dependency> diff --git a/goofy-server/src/main/java/de/itvsh/goofy/RootController.java b/goofy-server/src/main/java/de/itvsh/goofy/RootController.java index 6c92d0e8d98d4630d77d6f1c3abe94c74a2a9378..a069ce8261bddc575d47c03d52d18f0418f35fe6 100644 --- a/goofy-server/src/main/java/de/itvsh/goofy/RootController.java +++ b/goofy-server/src/main/java/de/itvsh/goofy/RootController.java @@ -26,6 +26,7 @@ package de.itvsh.goofy; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; import java.time.Instant; +import java.util.Objects; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; @@ -41,8 +42,8 @@ import de.itvsh.goofy.common.downloadtoken.DownloadTokenController; import de.itvsh.goofy.common.user.CurrentUserService; import de.itvsh.goofy.common.user.UserId; import de.itvsh.goofy.common.user.UserManagerUrlProvider; -import de.itvsh.goofy.common.user.UserRemoteService; import de.itvsh.goofy.common.user.UserRole; +import de.itvsh.goofy.common.user.UserService; import de.itvsh.goofy.system.SystemStatusService; import de.itvsh.goofy.vorgang.VorgangController; @@ -69,40 +70,35 @@ public class RootController { @Autowired private SystemStatusService systemStatusService; @Autowired - private UserRemoteService internalUserIdService; + private UserService userService; @Autowired private UserManagerUrlProvider userManagerUrlProvider; @GetMapping public EntityModel<RootResource> getRootResource() { - var internalUserId = internalUserIdService.getUserId(currentUserService.getUserId()); + var internalUserId = userService.getInternalId(currentUserService.getUserId()); - var modelBuilder = ModelBuilder.fromEntity(new RootResource()) + return ModelBuilder.fromEntity(new RootResource()) .ifMatch(this::hasRole).addLinks( linkTo(RootController.class).withSelfRel(), linkTo(VorgangController.class).withRel(REL_VORGAENGE), linkTo(DownloadTokenController.class).withRel(REL_DOWNLOAD_TOKEN), Link.of(userManagerUrlProvider.getUserProfileSearchTemplate() - .queryParam(USER_PROFILE_SEARCH_DELETED_PARAM, false) - .build(false) - .toUriString(), + .queryParam(USER_PROFILE_SEARCH_DELETED_PARAM, false) + .build(false) + .toUriString(), REL_SEARCH_USER)) .ifMatch(this::hasRoleAndSearchServerAvailable).addLinks( - buildVorgangListByPageLink(REL_SEARCH, Optional.empty())); - - internalUserId.ifPresent(userId -> modelBuilder - .ifMatch(this::hasVerwaltungRole).addLink( - buildVorgangListByPageLink(REL_MY_VORGAENGE, Optional.of(userId))) - .ifMatch(this::hasVerwaltungRoleAndSearchServerAvailable).addLink( - buildVorgangListByPageLink(REL_SEARCH_MY_VORGAENGE, Optional.of(userId)))); - - var model = modelBuilder.buildModel(); - - getUserProfilesUrl() - .ifPresent(urlTemplate -> model.add(Link.of(String.format(urlTemplate, currentUserService.getUserId()), REL_CURRENT_USER))); - - return model; + buildVorgangListByPageLink(REL_SEARCH, Optional.empty())) + .ifMatch(() -> Objects.nonNull(internalUserId) && hasVerwaltungRole()) + .addLink(() -> buildVorgangListByPageLink(REL_MY_VORGAENGE, Optional.of(internalUserId))) + .ifMatch(() -> Objects.nonNull(internalUserId) && hasVerwaltungRoleAndSearchServerAvailable()) + .addLink(() -> buildVorgangListByPageLink(REL_SEARCH_MY_VORGAENGE, Optional.of(internalUserId))) + .ifMatch(userManagerUrlProvider::isConfiguredForUserProfile) + .addLink(() -> Link.of(String.format(userManagerUrlProvider.getUserProfileTemplate(), currentUserService.getUserId()), + REL_CURRENT_USER)) + .buildModel(); } private boolean hasRoleAndSearchServerAvailable() { @@ -126,14 +122,6 @@ public class RootController { return linkTo(methodOn(VorgangController.class).getVorgangListByPage(0, null, null, assignedTo)).withRel(linkRel); } - Optional<String> getUserProfilesUrl() { - if (userManagerUrlProvider.isConfiguredForUserProfile()) { - return Optional.of(userManagerUrlProvider.getUserProfileTemplate()); - } - - return Optional.empty(); - } - class RootResource { public String getVersion() { diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ExceptionController.java b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ExceptionController.java index bc6076da1206786609275e87069265e5bbef0dca..9c5333d0dd19f9336f94a96b8e5110b149db3e9a 100644 --- a/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ExceptionController.java +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/errorhandling/ExceptionController.java @@ -55,7 +55,7 @@ import lombok.extern.log4j.Log4j2; @ControllerAdvice @Log4j2 -@Order(99) +@Order(97) public class ExceptionController { private static final Set<String> IGNORABLE_CONSTRAINT_VIOLATION_ATTRIBUTES = new HashSet<>(Arrays.asList("groups", "payload", "message")); diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserProfileMapper.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserProfileMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..5711c71ef1f9fd4980c18bbd8b9e4c002c8e1fb9 --- /dev/null +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserProfileMapper.java @@ -0,0 +1,15 @@ +package de.itvsh.goofy.common.user; + +import org.mapstruct.Mapper; + +import de.itvsh.kop.user.userprofile.GrpcUserProfile; + +@Mapper +interface UserProfileMapper { + + UserProfile mapFrom(GrpcUserProfile userProfile); + + default UserId toUserId(String userId) { + return UserId.from(userId); + } +} diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRemoteService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRemoteService.java index daf0de0433d00666402be4ad21c067a8d2a46db5..e46db2510839fa2a8aa2dcb5b3ae0e90b134ebed 100644 --- a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRemoteService.java +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserRemoteService.java @@ -23,110 +23,30 @@ */ package de.itvsh.goofy.common.user; -import java.util.LinkedHashMap; -import java.util.Optional; -import java.util.function.Supplier; - -import org.apache.commons.lang3.StringUtils; -import org.keycloak.KeycloakPrincipal; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.stereotype.Service; -import de.itvsh.goofy.common.errorhandling.MessageCode; -import de.itvsh.goofy.common.errorhandling.ServiceUnavailableException; -import lombok.extern.log4j.Log4j2; +import de.itvsh.kop.user.grpc.userprofile.GrpcGetUserProfileRequest; +import de.itvsh.kop.user.grpc.userprofile.UserProfileServiceGrpc.UserProfileServiceBlockingStub; +import net.devh.boot.grpc.client.inject.GrpcClient; -@Log4j2 -@Component +@Service public class UserRemoteService { - static final String FIRST_NAME_KEY = "firstName"; - static final String LAST_NAME_KEY = "lastName"; + private static final String USER_MANAGER_GRPC_CLIENT = "user-manager"; + @GrpcClient(USER_MANAGER_GRPC_CLIENT) + private UserProfileServiceBlockingStub userServiceStub; @Autowired - private UserManagerProperties userManagerProperties; - @Autowired - private UserManagerUrlProvider userManagerUrlProvider; - - private RestTemplate restTemplate = new RestTemplate(); - - public Optional<UserId> getUserId(UserId externalUserId) { - try { - if (userManagerUrlProvider.isConfiguredForInternalUserId()) { - var internalId = restTemplate.getForObject(userManagerProperties.getFullInternalUrlTemplate(), String.class, - externalUserId.toString()); - return StringUtils.isNotEmpty(internalId) ? Optional.of(UserId.from(internalId)) : Optional.empty(); - } else { - return Optional.empty(); - } - } catch (RestClientException e) { - LOG.warn("Error loading internal Userid.", e); - return Optional.empty(); - } - } - - public UserProfile getUser(UserId userId) { - return executeHandlingException(() -> getUserById(userId)); - } - - private <T> T executeHandlingException(Supplier<T> runnable) { - try { - return runnable.get(); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() == HttpStatus.NOT_FOUND) { - return null; - } - LOG.error("HttpClientErrorException: Error getting User by id.", e); - throw new ServiceUnavailableException(MessageCode.USER_MANAGER_SERVICE_UNAVAILABLE, e); - } catch (IllegalArgumentException e) { - LOG.error("IllegalArgumentException: Error getting User by id.", e); - throw new ServiceUnavailableException(MessageCode.USER_MANAGER_SERVICE_UNAVAILABLE, e); - } - } + private UserProfileMapper mapper; - private UserProfile getUserById(UserId userId) { - return buildUser(getBodyMap(doExchange(userId))); - } - - private ResponseEntity<Object> doExchange(UserId userId) { - return restTemplate.exchange(buildUserProfileUri(userId), HttpMethod.GET, buildHttpEntityWithAuthorization(), Object.class); - } - - String buildUserProfileUri(UserId userId) { - return UriComponentsBuilder.fromUriString(String.format(userManagerUrlProvider.getInternalUserProfileTemplate(), userId.toString())) - .toUriString(); - } - - private HttpEntity<Object> buildHttpEntityWithAuthorization() { - var headers = new HttpHeaders(); - headers.add("Authorization", "Bearer " + getToken()); - return new HttpEntity<>(headers); - } - - String getToken() { - var principle = (KeycloakPrincipal<?>) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - return principle.getKeycloakSecurityContext().getTokenString(); - } + public UserProfile getById(UserId externalUserId) { + var response = userServiceStub.getById(buildRequest(externalUserId)); - @SuppressWarnings("unchecked") - <T> LinkedHashMap<String, Object> getBodyMap(ResponseEntity<T> responseEntity) { - return (LinkedHashMap<String, Object>) responseEntity.getBody(); + return mapper.mapFrom(response.getUserProfile()); } - UserProfile buildUser(LinkedHashMap<String, Object> bodyMap) { - return UserProfile.builder() - .firstName((String) bodyMap.getOrDefault(FIRST_NAME_KEY, StringUtils.EMPTY)) - .lastName((String) bodyMap.getOrDefault(LAST_NAME_KEY, StringUtils.EMPTY)) - .build(); + private GrpcGetUserProfileRequest buildRequest(UserId externalUserId) { + return GrpcGetUserProfileRequest.newBuilder().setUserId(externalUserId.toString()).build(); } -} +} \ No newline at end of file diff --git a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserService.java b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserService.java index 4c970317e1433ca620e90cb9c83971987b1bdde6..419a83dd8ae7a643cc92f59b97be681a8767a8bc 100644 --- a/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserService.java +++ b/goofy-server/src/main/java/de/itvsh/goofy/common/user/UserService.java @@ -26,6 +26,13 @@ package de.itvsh.goofy.common.user; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import de.itvsh.goofy.common.errorhandling.MessageCode; +import de.itvsh.goofy.common.errorhandling.ServiceUnavailableException; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import lombok.extern.log4j.Log4j2; + +@Log4j2 @Service public class UserService { @@ -33,6 +40,27 @@ public class UserService { private UserRemoteService remoteService; public UserProfile getById(UserId userId) { - return remoteService.getUser(userId); + try { + return remoteService.getById(userId); + } catch (StatusRuntimeException e) { + if (isNotFound(e)) { + return null; + } + throw new ServiceUnavailableException(MessageCode.USER_MANAGER_SERVICE_UNAVAILABLE, e); + } + } + + private boolean isNotFound(StatusRuntimeException e) { + return e.getStatus().getCode().value() == Status.NOT_FOUND.getCode().value(); + } + + public UserId getInternalId(UserId externalId) { + try { + var userProfile = remoteService.getById(externalId); + return userProfile.getId(); + } catch (Exception e) {// TODO pruefen welche Exception wirklich zurueckgegeben wird + LOG.warn("Error loading internal Userid.", e); + return null; + } } } \ No newline at end of file diff --git a/goofy-server/src/main/resources/application-remotekc.yml b/goofy-server/src/main/resources/application-remotekc.yml index a9c24daa3d8cf653c70c5e9700fae348d1cc5882..da5ee99125b5053ab81270ee622e5254d3b1d984 100644 --- a/goofy-server/src/main/resources/application-remotekc.yml +++ b/goofy-server/src/main/resources/application-remotekc.yml @@ -3,4 +3,4 @@ keycloak: resource: sh-kiel-dev-goofy public-client: true use-resource-role-mappings: true - auth-server-url: https://sso.dev.ozg-sh.de + auth-server-url: https://sso.dev.by.kop-cloud.de diff --git a/goofy-server/src/main/resources/application.yml b/goofy-server/src/main/resources/application.yml index dd6db3af8b0d9e7ae616277a31cb78b5c15a114d..38e8bd89fc4d9eb2a2ab8d6a10bc80e876cd145b 100644 --- a/goofy-server/src/main/resources/application.yml +++ b/goofy-server/src/main/resources/application.yml @@ -63,6 +63,9 @@ grpc: pluto: address: static://127.0.0.1:9090 negotiationType: PLAINTEXT + user-manager: + address: static://127.0.0.1:9000 + negotiationType: PLAINTEXT kop: auth: diff --git a/goofy-server/src/test/java/de/itvsh/goofy/RootControllerTest.java b/goofy-server/src/test/java/de/itvsh/goofy/RootControllerTest.java index 9dab37319a45efd8fd17c1515078fe4aac062021..4a51d2665c108e6181fab821e45f39df0bd4e93b 100644 --- a/goofy-server/src/test/java/de/itvsh/goofy/RootControllerTest.java +++ b/goofy-server/src/test/java/de/itvsh/goofy/RootControllerTest.java @@ -31,7 +31,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.time.LocalDateTime; import java.time.ZoneOffset; -import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -46,15 +45,15 @@ import org.springframework.hateoas.Link; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.util.UriComponentsBuilder; import de.itvsh.goofy.common.user.CurrentUserService; import de.itvsh.goofy.common.user.UserId; import de.itvsh.goofy.common.user.UserManagerUrlProvider; import de.itvsh.goofy.common.user.UserProfileTestFactory; -import de.itvsh.goofy.common.user.UserRemoteService; import de.itvsh.goofy.common.user.UserRole; +import de.itvsh.goofy.common.user.UserService; import de.itvsh.goofy.system.SystemStatusService; -import org.springframework.web.util.UriComponentsBuilder; class RootControllerTest { @@ -68,7 +67,7 @@ class RootControllerTest { @Mock private SystemStatusService systemStatusService; @Mock - private UserRemoteService internalUserIdService; + private UserService userService; @Mock private UserManagerUrlProvider userManagerUrlProvider; @@ -79,7 +78,7 @@ class RootControllerTest { mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); when(currentUserService.getUserId()).thenReturn(UserId.from("42")); - when(internalUserIdService.getUserId(any())).thenReturn(Optional.empty()); + when(userService.getInternalId(any())).thenReturn(null); when(userManagerUrlProvider.getUserProfileSearchTemplate()).thenReturn( UriComponentsBuilder.fromUriString("UserProfileSearchTemplateDummy/$")); } @@ -123,7 +122,7 @@ class RootControllerTest { @Test void shouldHaveMyVorgaengeLink() { - when(internalUserIdService.getUserId(any())).thenReturn(Optional.of(UserProfileTestFactory.ID)); + when(userService.getInternalId(any())).thenReturn(UserProfileTestFactory.ID); var model = controller.getRootResource(); @@ -133,7 +132,7 @@ class RootControllerTest { @Test void shouldHaveSearchMyVorgaengeLink() { - when(internalUserIdService.getUserId(any())).thenReturn(Optional.of(UserProfileTestFactory.ID)); + when(userService.getInternalId(any())).thenReturn(UserProfileTestFactory.ID); var model = controller.getRootResource(); @@ -167,7 +166,7 @@ class RootControllerTest { @Test void shouldHaveMyVorgaengeLink() { - when(internalUserIdService.getUserId(any())).thenReturn(Optional.of(UserProfileTestFactory.ID)); + when(userService.getInternalId(any())).thenReturn(UserProfileTestFactory.ID); var model = controller.getRootResource(); @@ -293,7 +292,7 @@ class RootControllerTest { void init() { doReturn(true).when(controller).hasVerwaltungRole(); - when(internalUserIdService.getUserId(any())).thenReturn(Optional.empty()); + when(userService.getInternalId(any())).thenReturn(null); when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(false); } @@ -340,7 +339,7 @@ class RootControllerTest { @BeforeEach void initTest() { when(currentUserService.getUserId()).thenReturn(UserId.from("42")); - when(internalUserIdService.getUserId(any())).thenReturn(Optional.empty()); + when(userService.getInternalId(any())).thenReturn(null); when(userManagerUrlProvider.getUserProfileSearchTemplate()).thenReturn( UriComponentsBuilder.fromUriString("UserProfileSearchTemplateDummy/$")); } diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/user/GrpcUserProfileTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/user/GrpcUserProfileTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..1c2e08459a58128c2f5e46552831ac451f0c043a --- /dev/null +++ b/goofy-server/src/test/java/de/itvsh/goofy/common/user/GrpcUserProfileTestFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.itvsh.goofy.common.user; + +import de.itvsh.kop.user.grpc.userprofile.GrpcGetUserProfileRequest; +import de.itvsh.kop.user.grpc.userprofile.GrpcGetUserProfileResponse; +import de.itvsh.kop.user.userprofile.GrpcUserProfile; + +public class GrpcUserProfileTestFactory { + + public static GrpcGetUserProfileRequest createGetUserProfileRequest() { + return GrpcGetUserProfileRequest.newBuilder().setUserId(UserProfileTestFactory.ID.toString()).build(); + } + + public static GrpcGetUserProfileResponse createGetUserProfileResponse() { + return GrpcGetUserProfileResponse.newBuilder().setUserProfile(create()).build(); + } + + public static GrpcUserProfile create() { + return createBuilder().build(); + } + + public static GrpcUserProfile.Builder createBuilder() { + return GrpcUserProfile.newBuilder() + .setId(UserProfileTestFactory.ID.toString()) + .setFirstName(UserProfileTestFactory.FIRSTNAME) + .setLastName(UserProfileTestFactory.LASTNAME); + } +} \ No newline at end of file diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/user/GrpcUserTestFactory.java b/goofy-server/src/test/java/de/itvsh/goofy/common/user/GrpcUserTestFactory.java index d4aa272f6699fc73f3595545e4ebce88dcbd44f3..8b749807f1b82e50029c06dc1d836374d76061bd 100644 --- a/goofy-server/src/test/java/de/itvsh/goofy/common/user/GrpcUserTestFactory.java +++ b/goofy-server/src/test/java/de/itvsh/goofy/common/user/GrpcUserTestFactory.java @@ -1,26 +1,3 @@ -/* - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ package de.itvsh.goofy.common.user; import java.util.Collections; diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserProfileMapperTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserProfileMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3e89b0e8311fa67b0cea66656d09fd392670b823 --- /dev/null +++ b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserProfileMapperTest.java @@ -0,0 +1,51 @@ +package de.itvsh.goofy.common.user; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +import de.itvsh.kop.user.userprofile.GrpcUserProfile; + +class UserProfileMapperTest { + + private UserProfileMapper mapper = Mappers.getMapper(UserProfileMapper.class); + + @DisplayName("Map from") + @Nested + class TestMapFrom { + + @Test + void shouldMapFirstName() { + var userProfile = map(); + + assertThat(userProfile.getFirstName()).isEqualTo(UserProfileTestFactory.FIRSTNAME); + } + + @Test + void shouldMapLastName() { + var userProfile = map(); + + assertThat(userProfile.getLastName()).isEqualTo(UserProfileTestFactory.LASTNAME); + } + + @Test + void shouldMapId() { + var userProfile = map(); + + assertThat(userProfile.getId()).isEqualTo(UserProfileTestFactory.ID); + } + + private UserProfile map() { + return mapper.mapFrom(GrpcUserProfileTestFactory.create()); + } + + @Test + void shouldProceedWithNull() { + Assertions.assertDoesNotThrow(() -> mapper.mapFrom(GrpcUserProfile.newBuilder().build())); + } + } +} diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserRemoteServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserRemoteServiceTest.java index 5d31612f2a7ed0d0b52cf2b09407bf0c9030d035..930e8c3639015f053e515313ba388f00587f2873 100644 --- a/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserRemoteServiceTest.java +++ b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserRemoteServiceTest.java @@ -27,26 +27,18 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import java.util.LinkedHashMap; -import java.util.Map; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; -import de.itvsh.goofy.common.errorhandling.ServiceUnavailableException; +import de.itvsh.kop.user.grpc.userprofile.GrpcGetUserProfileRequest; +import de.itvsh.kop.user.grpc.userprofile.UserProfileServiceGrpc.UserProfileServiceBlockingStub; class UserRemoteServiceTest { @@ -54,208 +46,43 @@ class UserRemoteServiceTest { @InjectMocks private UserRemoteService service; @Mock - private UserManagerProperties userManagerProperties; - @Mock - private UserManagerUrlProvider userManagerUrlProvider; - + private UserProfileServiceBlockingStub serviceStub; @Mock - private RestTemplate restTemplate; - - @DisplayName("Get userId") - @Nested - class TestGetUserId { - - private final String internalUrlTemplate = "DummyInternalUrlTemplate"; - - @DisplayName("with configured usermanager") - @Nested - class TestWithConfiguredUserManager { - - @BeforeEach - void mock() { - when(userManagerProperties.getFullInternalUrlTemplate()).thenReturn(internalUrlTemplate); - when(userManagerUrlProvider.isConfiguredForInternalUserId()).thenReturn(true); - } - - @DisplayName("on valid response") - @Nested - class TestSuccess { - - @BeforeEach - void mock() { - when(restTemplate.getForObject(anyString(), eq(String.class), anyString())).thenReturn(UserProfileTestFactory.ID.toString()); - } - - @Test - void shouldReturnResponseAsUserId() { - var userId = service.getUserId(UserProfileTestFactory.ID); - - assertThat(userId).hasValue(UserProfileTestFactory.ID); - } - } - - @DisplayName("on error response") - @Nested - class TestErrorCases { - - @Test - void shouldHandleEmptyValue() { - when(restTemplate.getForObject(anyString(), eq(String.class), anyString())).thenReturn(""); - - var res = service.getUserId(UserProfileTestFactory.ID); - - assertThat(res).isNotPresent(); - } - - @Test - void shouldHandleError() { - when(restTemplate.getForObject(anyString(), eq(String.class), anyString())).thenThrow(new RestClientException("Test error")); - - var res = service.getUserId(UserProfileTestFactory.ID); - - assertThat(res).isNotPresent(); - } - } - } - - @DisplayName("with not configured usermanager") - @Nested - class TestOnNotConfiguredUserManager { - - @BeforeEach - void mock() { - when(userManagerUrlProvider.isConfiguredForInternalUserId()).thenReturn(false); - } - - @Test - void shouldNotCallUserManagerProperties() { - service.getUserId(UserProfileTestFactory.ID); - - verify(userManagerProperties, never()).getFullInternalUrlTemplate(); - } - - @Test - void shouldReturnEmptyOptional() { - var user = service.getUserId(UserProfileTestFactory.ID); - - assertThat(user).isNotPresent(); - } - } - } + private UserProfileMapper mapper; - @DisplayName("Get user") + @DisplayName("Find by id") @Nested - class TestGetUser { + class TestFindById { - private final String profileUri = "DummyProfileTemplate/" + UserProfileTestFactory.ID; - private final String dummyToken = "Token"; + @Captor + private ArgumentCaptor<GrpcGetUserProfileRequest> requestCaptor; @BeforeEach void mock() { - doReturn(profileUri).when(service).buildUserProfileUri(any()); - doReturn(dummyToken).when(service).getToken(); + when(serviceStub.getById(any())).thenReturn(GrpcUserProfileTestFactory.createGetUserProfileResponse()); + when(mapper.mapFrom(any())).thenReturn(UserProfileTestFactory.create()); } - @DisplayName("on valid response") - @Nested - class TestOnValidResponse { - - private final Map<String, Object> bodyMap = new LinkedHashMap<>(Map.of(UserRemoteService.FIRST_NAME_KEY, UserProfileTestFactory.FIRSTNAME, - UserRemoteService.LAST_NAME_KEY, UserProfileTestFactory.LASTNAME)); - private final ResponseEntity<Object> response = new ResponseEntity<>(bodyMap, HttpStatus.OK); - - @BeforeEach - void mock() { - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(), eq(Object.class))).thenReturn(response); - } - - @Test - void shouldCallRestTemplate() { - var headers = new HttpHeaders(); - headers.add("Authorization", "Bearer " + dummyToken); - var httpEntity = new HttpEntity<>(headers); - - service.getUser(UserProfileTestFactory.ID); - - verify(restTemplate).exchange(profileUri, HttpMethod.GET, httpEntity, Object.class); - } - - @Test - void shouldBuildUrl() { - service.getUser(UserProfileTestFactory.ID); - - verify(service).buildUserProfileUri(UserProfileTestFactory.ID); - } - - @Test - void shouldReturnUser() { - var loadedUser = service.getUser(UserProfileTestFactory.ID); - - assertThat(loadedUser.getFirstName()).isEqualTo(UserProfileTestFactory.FIRSTNAME); - assertThat(loadedUser.getLastName()).isEqualTo(UserProfileTestFactory.LASTNAME); - } - } - - @DisplayName("on error response") - @Nested - class TestOnErrorResponse { - - private final HttpClientErrorException httpClientErrorException = new HttpClientErrorException(HttpStatus.SERVICE_UNAVAILABLE, - "Test error"); - private final IllegalArgumentException illegalArgumentException = new IllegalArgumentException(); - - private final HttpClientErrorException notFoundException = new HttpClientErrorException(HttpStatus.NOT_FOUND, "Test error"); - - @Test - void shouldThrowServiceUnavailablExceptionOnException() { - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(), eq(Object.class))).thenThrow(httpClientErrorException); - - assertThatThrownBy(() -> service.getUser(UserProfileTestFactory.ID)).isInstanceOf(ServiceUnavailableException.class) - .hasCause(httpClientErrorException); - } - - @Test - void shouldThrowServiceUnavailablExceptionOnIlleglaArgumentException() { - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(), eq(Object.class))).thenThrow(illegalArgumentException); - - assertThatThrownBy(() -> service.getUser(UserProfileTestFactory.ID)).isInstanceOf(ServiceUnavailableException.class) - .hasCause(illegalArgumentException); - } - - @Test - void shouldReturnEmptyOptionalOnNotFoundException() { - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(), eq(Object.class))).thenThrow(notFoundException); - - var user = service.getUser(UserProfileTestFactory.ID); - - assertThat(user).isNull(); - } - } - } - - @DisplayName("Build user profile uri") - @Nested - class TestBuildUserProfileUri { - - private final String profileUriTemplate = "DummyProfileTemplate/%s"; + @Test + void shouldCallRemoteService() { + service.getById(UserProfileTestFactory.ID); - @BeforeEach - void mock() { - when(userManagerUrlProvider.getInternalUserProfileTemplate()).thenReturn(profileUriTemplate); + verify(serviceStub).getById(requestCaptor.capture()); + assertThat(requestCaptor.getValue().getUserId()).isEqualTo(UserProfileTestFactory.ID.toString()); } @Test - void shouldCallUserManagerUrlProvider() { - service.buildUserProfileUri(UserProfileTestFactory.ID); + void shouldCallMapper() { + service.getById(UserProfileTestFactory.ID); - verify(userManagerUrlProvider).getInternalUserProfileTemplate(); + verify(mapper).mapFrom(GrpcUserProfileTestFactory.create()); } @Test - void shouldReturnUserProfileUri() { - var uri = service.buildUserProfileUri(UserProfileTestFactory.ID); + void shouldReturnValue() { + var userProfile = service.getById(UserProfileTestFactory.ID); - assertThat(uri).isEqualTo("DummyProfileTemplate/" + UserProfileTestFactory.ID); + assertThat(userProfile).usingRecursiveComparison().isEqualTo(UserProfileTestFactory.create()); } } } \ No newline at end of file diff --git a/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserServiceTest.java b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserServiceTest.java index d8092e887d3b4bd0bb20b48a159fa994a3014915..7d242ba7f6504b53d909aeae65e803891a91f32c 100644 --- a/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserServiceTest.java +++ b/goofy-server/src/test/java/de/itvsh/goofy/common/user/UserServiceTest.java @@ -23,14 +23,24 @@ */ package de.itvsh.goofy.common.user; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; import org.mockito.InjectMocks; import org.mockito.Mock; +import de.itvsh.goofy.common.errorhandling.MessageCode; +import de.itvsh.goofy.common.errorhandling.ServiceUnavailableException; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; + class UserServiceTest { @InjectMocks @@ -44,9 +54,58 @@ class UserServiceTest { @Test void shouldCallRemoteService() { - service.getById(UserProfileTestFactory.ID); + getById(); + + verify(remoteService).getById(UserProfileTestFactory.ID); + } + + @Test + void shouldReturnNullOnNotFoundException() { + when(remoteService.getById(any())).thenThrow(new StatusRuntimeException(Status.NOT_FOUND)); + + var userProfile = getById(); + + assertThat(userProfile).isNull(); + } + + @DisplayName("should throw unavailable exception if exception is other than not found") + @ParameterizedTest + @EnumSource(mode = Mode.EXCLUDE, names = { "NOT_FOUND" }) + void shouldThrowServiceUnavailableException(Status.Code status) { + when(remoteService.getById(any())).thenThrow(new StatusRuntimeException(Status.fromCode(status))); + + assertThatThrownBy(() -> getById()) + .isInstanceOf(ServiceUnavailableException.class) + .extracting(e -> ((ServiceUnavailableException) e).getMessageCode()).isEqualTo(MessageCode.USER_MANAGER_SERVICE_UNAVAILABLE); + } + + private UserProfile getById() { + return service.getById(UserProfileTestFactory.ID); + } + } + + @DisplayName("Get internalId") + @Nested + class TestGetInternalId { + + @Test + void shouldCallRemoteService() { + getInternalId(); + + verify(remoteService).getById(UserProfileTestFactory.ID); + } + + @Test + void shouldReturnNullOnException() { + when(remoteService.getById(any())).thenThrow(new StatusRuntimeException(Status.UNAVAILABLE)); + + var userProfile = getInternalId(); + + assertThat(userProfile).isNull(); + } - verify(remoteService).getUser(UserProfileTestFactory.ID); + private UserId getInternalId() { + return service.getInternalId(UserProfileTestFactory.ID); } } -} +} \ No newline at end of file diff --git a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfServiceITCase.java b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfServiceITCase.java index a4ada1e59fa75268a52d42cef5e90924d605d3c3..2b0134f6bbc063569dad88718fcbf952447a2da8 100644 --- a/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfServiceITCase.java +++ b/goofy-server/src/test/java/de/itvsh/goofy/postfach/PostfachNachrichtPdfServiceITCase.java @@ -64,7 +64,7 @@ class PostfachNachrichtPdfServiceITCase { @BeforeEach void mock() { - when(userRemoteService.getUser(any(UserId.class))).thenReturn(UserProfileTestFactory.create()); + when(userRemoteService.getById(any(UserId.class))).thenReturn(UserProfileTestFactory.create()); } @SneakyThrows diff --git a/pom.xml b/pom.xml index 9471c424726c22a57a10a9d937205eaefdb39015..12c913062f23938e1e3ecff70563c6f6cab83891 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ <parent> <groupId>de.itvsh.kop.common</groupId> <artifactId>kop-common-parent</artifactId> - <version>1.3.0</version> + <version>1.4.0-SNAPSHOT</version> </parent> <modules> @@ -53,6 +53,7 @@ <pluto.version>1.3.0-SNAPSHOT</pluto.version> <jsoup.version>1.15.1</jsoup.version> <kop-common-pdf.version>1.3.0</kop-common-pdf.version> + <user-manager.version>1.3.0-SNAPSHOT</user-manager.version> </properties> <build> @@ -114,6 +115,21 @@ <artifactId>jsoup</artifactId> <version>${jsoup.version}</version> </dependency> + <dependency> + <groupId>de.itvsh.kop.user</groupId> + <artifactId>user-manager-interface</artifactId> + <version>${user-manager.version}</version> + <exclusions> + <exclusion> + <groupId>io.grpc</groupId> + <artifactId>grpc-core</artifactId> + </exclusion> + <exclusion> + <groupId>org.jboss.slf4j</groupId> + <artifactId>slf4j-jboss-logmanager</artifactId> + </exclusion> + </exclusions> + </dependency> </dependencies> </dependencyManagement>