diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/LinkedUserProfileResourceSerializer.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/LinkedUserProfileResourceSerializer.java index ed0f0ee2936cd65ee0e34587616ae67c2af65183..582ad419b2abd5a160e3b5ceb124d84114c49d87 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/LinkedUserProfileResourceSerializer.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/LinkedUserProfileResourceSerializer.java @@ -29,6 +29,7 @@ import java.util.Collection; import org.apache.commons.lang3.reflect.ConstructorUtils; import org.springframework.hateoas.Link; +import org.springframework.stereotype.Component; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.BeanProperty; @@ -36,37 +37,27 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import de.ozgcloud.alfa.common.user.UserManagerUrlProvider; import de.ozgcloud.common.errorhandling.TechnicalException; -import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; -@NoArgsConstructor +@Component +@RequiredArgsConstructor public class LinkedUserProfileResourceSerializer extends JsonSerializer<Object> implements ContextualSerializer { + private final UserManagerUrlProvider userManagerUrlProvider; + private LinkedUserProfileResource annotation; - private LinkedUserProfileResourceSerializer(LinkedUserProfileResource annotation) { - this.annotation = annotation; + static LinkedUserProfileResourceSerializer createForAnnotatedField(UserManagerUrlProvider userManagerUrlProvider, LinkedUserProfileResource annotation) { + var serializer = new LinkedUserProfileResourceSerializer(userManagerUrlProvider); + serializer.annotation = annotation; + return serializer; } @Override public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) { - return new LinkedUserProfileResourceSerializer(property.getAnnotation(LinkedUserProfileResource.class)); - } - - String buildLink(Object id) { - if (UserProfileUrlProvider.isConfigured()) { - return Link.of(UserProfileUrlProvider.getUrl(getExtractor().extractId(id))).getHref(); - } else { - return id.toString(); - } - } - - IdExtractor<Object> getExtractor() { - try { - return ConstructorUtils.invokeConstructor(annotation.extractor()); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { - throw new TechnicalException("Error instanciating IdExtractor", e); - } + return LinkedUserProfileResourceSerializer.createForAnnotatedField(userManagerUrlProvider, property.getAnnotation(LinkedUserProfileResource.class)); } @Override @@ -80,7 +71,25 @@ public class LinkedUserProfileResourceSerializer extends JsonSerializer<Object> } } - void writeObject(JsonGenerator gen, Object value) { + private String buildLink(Object id) { + return userManagerUrlProvider.isConfiguredForUserProfile() ? + Link.of(buildUserProfileUri(id.toString())).getHref() : + String.valueOf(id); + } + + private String buildUserProfileUri(String id) { + return String.format(userManagerUrlProvider.getUserProfileTemplate(), getExtractor().extractId(id)); + } + + private IdExtractor<Object> getExtractor() { + try { + return ConstructorUtils.invokeConstructor(annotation.extractor()); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { + throw new TechnicalException("Error instanciating IdExtractor", e); + } + } + + private void writeObject(JsonGenerator gen, Object value) { try { gen.writeObject(value); } catch (IOException e) { diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/LinkedUserProfileResourceSerializerITCase.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/LinkedUserProfileResourceSerializerITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..ca3058226b1c21a358db5a68e879a7869fb6b339 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/LinkedUserProfileResourceSerializerITCase.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 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.ozgcloud.alfa.common; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.ozgcloud.alfa.common.user.UserManagerUrlProvider; +import de.ozgcloud.alfa.common.user.UserProfileTestFactory; +import de.ozgcloud.common.test.ITCase; + +@ITCase +class LinkedUserProfileResourceSerializerITCase { + + private static final String HTTP_LOCALHOST = "http://localhost/"; + private static final String API_TEMPLATE = "api/profile/%s"; + private static final String API_PATH = "api/profile/"; + + @MockitoBean + private UserManagerUrlProvider userManagerUrlProvider; + + @Autowired + private ObjectMapper objectMapper; + + @BeforeEach + void init() { + when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(true); + when(userManagerUrlProvider.getUserProfileTemplate()).thenReturn(HTTP_LOCALHOST + API_TEMPLATE); + } + + @Test + void shouldSerialize() throws JsonProcessingException { + var testObj = new LinkedUserProfileResourceTestObject(UserProfileTestFactory.ID); + + var serialized = objectMapper.writeValueAsString(testObj); + + assertThat(serialized).isEqualTo("{\"id\":\"" + HTTP_LOCALHOST + API_PATH + UserProfileTestFactory.ID.toString() + "\"}"); + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/LinkedUserProfileResourceSerializerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/LinkedUserProfileResourceSerializerTest.java index 6a7ebb175d136027b76138fa4d0c6aea6544108c..31f1031218762d56ea641f5feb4e116555fe3420 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/LinkedUserProfileResourceSerializerTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/LinkedUserProfileResourceSerializerTest.java @@ -1,71 +1,177 @@ -/* - * Copyright (C) 2023 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.ozgcloud.alfa.common; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import org.junit.jupiter.api.DisplayName; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; -import org.springframework.context.ApplicationContext; -import org.springframework.core.env.Environment; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; -import de.ozgcloud.alfa.common.user.UserProfileTestFactory; +import de.ozgcloud.alfa.common.user.UserId; +import de.ozgcloud.alfa.common.user.UserManagerUrlProvider; +import lombok.SneakyThrows; class LinkedUserProfileResourceSerializerTest { - @DisplayName("Test the json serilization of linked user profile resource annotations") + + private static final LinkedUserProfileResource ANNOTATION = getAnnotation(); + + @Mock + private UserManagerUrlProvider userManagerUrlProvider; + + private LinkedUserProfileResourceSerializer serializer; + + @BeforeEach + void init() { + serializer = LinkedUserProfileResourceSerializer.createForAnnotatedField(userManagerUrlProvider, ANNOTATION); + } + @Nested - class TestSerialization { - private static final String HTTP_LOCALHOST = "http://localhost/"; - private static final String API_TEMPLATE = "api/profile/%s"; - private static final String API_PATH = "api/profile/"; + class TestSerialize { - private UserProfileUrlProvider provider = new UserProfileUrlProvider(); + private static final UserId USER_ID = UserId.from(UUID.randomUUID()); + private static final UserId USER_ID_2 = UserId.from(UUID.randomUUID()); @Mock - private ApplicationContext context; + private JsonGenerator generator; @Mock - private Environment env; + private SerializerProvider serializerProvider; + @Captor + private ArgumentCaptor<Object> writtenObjectCaptor; + + @Nested + class OnUrlIsConfigured { + + private static final String HTTP_LOCALHOST = "http://localhost/"; + private static final String API_TEMPLATE = "api/profile/%s"; + private static final String API_PATH = "api/profile/"; - @Test - void shouldSerialize() throws JsonProcessingException { - when(env.getProperty(UserProfileUrlProvider.URL_ROOT_KEY)).thenReturn(HTTP_LOCALHOST); - when(env.getProperty(UserProfileUrlProvider.USER_PROFILES_TEMPLATE_KEY)).thenReturn(API_TEMPLATE); - when(context.getEnvironment()).thenReturn(env); - provider.setApplicationContext(context); + @BeforeEach + void init() { + when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(true); + when(userManagerUrlProvider.getUserProfileTemplate()).thenReturn(HTTP_LOCALHOST + API_TEMPLATE); + } - var testObj = new LinkedUserProfileResourceTestObject(UserProfileTestFactory.ID); + @Nested + class OnSingularValue { - String serialized = new ObjectMapper().writeValueAsString(testObj); + @SneakyThrows + @Test + void shouldSerializeToUrl() { + serializeSingleValue(); - assertThat(serialized).isEqualTo("{\"id\":\"" + HTTP_LOCALHOST + API_PATH + UserProfileTestFactory.ID.toString() + "\"}"); + verify(generator).writeObject(writtenObjectCaptor.capture()); + assertThat(writtenObjectCaptor.getValue()).isEqualTo(expectedUrl(USER_ID)); + } + } + + @Nested + class OnCollection { + + @SneakyThrows + @Test + void shouldWriteStartArray() { + serializeCollection(); + + verify(generator).writeStartArray(); + } + + @SneakyThrows + @Test + void shouldSerializeToUrls() { + serializeCollection(); + + verify(generator, times(2)).writeObject(writtenObjectCaptor.capture()); + assertThat(writtenObjectCaptor.getAllValues()).containsExactly(expectedUrl(USER_ID), expectedUrl(USER_ID_2)); + } + + @SneakyThrows + @Test + void shouldWriteEndArray() { + serializeCollection(); + + verify(generator).writeEndArray(); + } + } + + private String expectedUrl(UserId id) { + return HTTP_LOCALHOST + API_PATH + id.toString(); + } } + @Nested + class OnUriIsNotConfigured { + + @BeforeEach + void init() { + when(userManagerUrlProvider.isConfiguredForUserProfile()).thenReturn(false); + } + + @Nested + class OnSingularValue { + + @SneakyThrows + @Test + void shouldSerializeToString() { + serializeSingleValue(); + + verify(generator).writeObject(writtenObjectCaptor.capture()); + assertThat(writtenObjectCaptor.getValue()).isEqualTo(USER_ID.toString()); + } + } + + @Nested + class OnCollection { + + @SneakyThrows + @Test + void shouldWriteStartArray() { + serializeCollection(); + + verify(generator).writeStartArray(); + } + + @SneakyThrows + @Test + void shouldSerializeToUrls() { + serializeCollection(); + + verify(generator, times(2)).writeObject(writtenObjectCaptor.capture()); + assertThat(writtenObjectCaptor.getAllValues()).containsExactly(USER_ID.toString(), USER_ID_2.toString()); + } + + @SneakyThrows + @Test + void shouldWriteEndArray() { + serializeCollection(); + + verify(generator).writeEndArray(); + } + } + } + + @SneakyThrows + private void serializeSingleValue() { + serializer.serialize(USER_ID, generator, serializerProvider); + } + + @SneakyThrows + private void serializeCollection() { + serializer.serialize(List.of(USER_ID, USER_ID_2), generator, serializerProvider); + } + } + + @SneakyThrows + private static LinkedUserProfileResource getAnnotation() { + var idField = LinkedUserProfileResourceTestObject.class.getDeclaredField("id"); + return idField.getDeclaredAnnotation(LinkedUserProfileResource.class); } }