/* * Copyright 2016 Black Pepper Software * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package uk.co.blackpepper.bowman; import java.util.List; import java.util.function.BiFunction; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.junit.Before; import org.junit.Test; import org.junit.experimental.theories.ParameterSignature; import org.junit.experimental.theories.ParameterSupplier; import org.junit.experimental.theories.ParametersSuppliedBy; import org.junit.experimental.theories.PotentialAssignment; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.KeyDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; import com.fasterxml.jackson.databind.jsontype.impl.MinimalClassNameIdResolver; import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder; import com.fasterxml.jackson.databind.type.SimpleType; import com.fasterxml.jackson.databind.type.TypeFactory; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(Theories.class) public class RestOperationsFactoryTest { private RestTemplateFactory restTemplateFactory; private ObjectMapperFactory mapperFactory; private ClientProxyFactory proxyFactory; private ClientHttpRequestFactory clientHttpRequestFactory; private RestOperationsFactory factory; private Configuration configuration; @Before public void setup() { restTemplateFactory = mock(RestTemplateFactory.class); mapperFactory = mock(ObjectMapperFactory.class); proxyFactory = mock(ClientProxyFactory.class); clientHttpRequestFactory = mock(ClientHttpRequestFactory.class); configuration = Configuration.builder() .setRestTemplateConfigurer(null) .setClientHttpRequestFactory(clientHttpRequestFactory) .build(); factory = new RestOperationsFactory(configuration, proxyFactory, mapperFactory, restTemplateFactory); when(mapperFactory.create(any())).thenReturn(new ObjectMapper()); when(restTemplateFactory.create(any(), any())).thenReturn(new RestTemplate()); } @Test public void createReturnsRestOperations() { ObjectMapper mapper = new ObjectMapper(); RestTemplate restTemplate = new RestTemplate(); when(mapperFactory.create(any())).thenReturn(mapper); when(restTemplateFactory.create(clientHttpRequestFactory, mapper)).thenReturn(restTemplate); RestOperations restOperations = factory.create(); assertThat(restOperations, is(aRestOperationsMatching(is(restTemplate), is(mapper)))); } @Test public void createInstantiatesObjectMapperWithInlineAssociationDeserializerAwareHandlerInstantiator() { ObjectMapper mapper = new ObjectMapper(); RestTemplate restTemplate = new RestTemplate(); when(mapperFactory.create(any())).thenReturn(mapper); when(restTemplateFactory.create(any(), any())).thenReturn(restTemplate); factory.create(); ArgumentCaptor<HandlerInstantiator> handlerInstantiator = ArgumentCaptor.forClass(HandlerInstantiator.class); verify(mapperFactory).create(handlerInstantiator.capture()); JsonDeserializer<?> result = handlerInstantiator.getValue() .deserializerInstance(null, null, InlineAssociationDeserializer.class); assertThat(result, is(anInlineAssociationDeserializerMatching( aRestOperationsMatching(is(restTemplate), is(mapper)), is(proxyFactory)))); } @Test public void createInstantiatesObjectMapperWithResourceDeserializerAwareHandlerInstantiator() { factory.create(); ArgumentCaptor<HandlerInstantiator> handlerInstantiator = ArgumentCaptor.forClass(HandlerInstantiator.class); verify(mapperFactory).create(handlerInstantiator.capture()); JsonDeserializer<?> result = handlerInstantiator.getValue() .deserializerInstance(null, null, ResourceDeserializer.class); assertThat(result, is(aResourceDeserializerMatching(instanceOf(DefaultTypeResolver.class), is(configuration)))); } @Theory public void createInstantiatesObjectMapperWithNonLibraryHandlerAwareHandlerInstantiator( @ParametersSuppliedBy(NonLibraryHandlerTestParams.class) HandlerInstantiatorTestParams params) { factory.create(); ArgumentCaptor<HandlerInstantiator> handlerInstantiator = ArgumentCaptor.forClass(HandlerInstantiator.class); verify(mapperFactory).create(handlerInstantiator.capture()); Object result = params.instantiationMethod.apply(handlerInstantiator.getValue(), params.clazz); assertThat(result, instanceOf(params.clazz)); } @Test public void createInvokesConfigurerOnRestTemplateIfPresent() { RestTemplateConfigurer restTemplateConfigurer = mock(RestTemplateConfigurer.class); Configuration configuration = Configuration.builder() .setRestTemplateConfigurer(restTemplateConfigurer) .build(); RestTemplate restTemplate = new RestTemplate(); when(restTemplateFactory.create(any(), any())).thenReturn(restTemplate); new RestOperationsFactory(configuration, proxyFactory, mapperFactory, restTemplateFactory) .create(); verify(restTemplateConfigurer).configure(restTemplate); } @Test public void createInvokesConfigurerOnObjectMapperIfPresent() { ObjectMapperConfigurer objectMapperConfigurer = mock(ObjectMapperConfigurer.class); Configuration configuration = Configuration.builder() .setObjectMapperConfigurer(objectMapperConfigurer) .build(); ObjectMapper objectMapper = new ObjectMapper(); when(mapperFactory.create(any())).thenReturn(objectMapper); new RestOperationsFactory(configuration, proxyFactory, mapperFactory, restTemplateFactory) .create(); verify(objectMapperConfigurer).configure(objectMapper); } private static Matcher<RestOperations> aRestOperationsMatching(Matcher<RestTemplate> restTemplate, Matcher<ObjectMapper> mapper) { return new TypeSafeMatcher<RestOperations>() { @Override public boolean matchesSafely(RestOperations other) { return restTemplate.matches(other.getRestTemplate()) && mapper.matches(other.getObjectMapper()); } @Override public void describeTo(Description description) { description.appendText("restTemplate ").appendValue(restTemplate) .appendText(", objectMapper ").appendValue(mapper); } }; } private static Matcher<JsonDeserializer> anInlineAssociationDeserializerMatching( Matcher<RestOperations> restOperations, Matcher<ClientProxyFactory> proxyFactory) { return new TypeSafeMatcher<JsonDeserializer>() { @Override public boolean matchesSafely(JsonDeserializer item) { if (!(item instanceof InlineAssociationDeserializer)) { return false; } InlineAssociationDeserializer other = (InlineAssociationDeserializer) item; return restOperations.matches(other.getRestOperations()) && proxyFactory.matches(other.getProxyFactory()); } @Override public void describeTo(Description description) { description.appendText("instanceof ").appendValue(InlineAssociationDeserializer.class) .appendText(", restOperations ").appendValue(restOperations) .appendText(", proxyFactory ").appendValue(proxyFactory); } }; } private static Matcher<JsonDeserializer> aResourceDeserializerMatching( Matcher<TypeResolver> typeResolver, Matcher<Configuration> configuration) { return new TypeSafeMatcher<JsonDeserializer>() { @Override protected boolean matchesSafely(JsonDeserializer item) { if (!(item instanceof ResourceDeserializer)) { return false; } ResourceDeserializer other = (ResourceDeserializer) item; return typeResolver.matches(other.getTypeResolver()) && configuration.matches(other.getConfiguration()); } @Override public void describeTo(Description description) { description.appendText("instanceof ").appendValue(ResourceDeserializer.class) .appendText(", typeResolver ").appendValue(typeResolver) .appendText(", configuration ").appendValue(configuration); } }; } private static class HandlerInstantiatorTestParams { private Class<?> clazz; private BiFunction<HandlerInstantiator, Class<?>, Object> instantiationMethod; HandlerInstantiatorTestParams(Class<?> clazz, BiFunction<HandlerInstantiator, Class<?>, Object> instantiationMethod) { this.clazz = clazz; this.instantiationMethod = instantiationMethod; } } public static class NonLibraryHandlerTestParams extends ParameterSupplier { public NonLibraryHandlerTestParams() { } @Override public List<PotentialAssignment> getValueSources(ParameterSignature sig) { return asList( PotentialAssignment.forValue( "deserializerInstance", new HandlerInstantiatorTestParams(DummyJsonDeserializer.class, (instantiator, clazz) -> instantiator.deserializerInstance(null, null, clazz)) ), PotentialAssignment.forValue( "keyDeserializerInstance", new HandlerInstantiatorTestParams(DummyKeyDeserializer.class, (instantiator, clazz) -> instantiator.keyDeserializerInstance(null, null, clazz)) ), PotentialAssignment.forValue( "serializerInstance", new HandlerInstantiatorTestParams(DummySerializer.class, (instantiator, clazz) -> instantiator.serializerInstance(null, null, clazz)) ), PotentialAssignment.forValue( "typeResolverBuilderInstance", new HandlerInstantiatorTestParams(DummyTypeResolverBuilder.class, (instantiator, clazz) -> instantiator.typeResolverBuilderInstance(null, null, clazz)) ), PotentialAssignment.forValue( "typeIdResolverInstance", new HandlerInstantiatorTestParams(DummyTypeIdResolver.class, (instantiator, clazz) -> instantiator.typeIdResolverInstance(null, null, clazz)) ) ); } } private static class DummyJsonDeserializer extends JsonDeserializer<Object> { @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) { return null; } } private static class DummyKeyDeserializer extends KeyDeserializer { @Override public Object deserializeKey(String key, DeserializationContext ctxt) { return null; } } private static class DummySerializer extends JsonSerializer<Object> { @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) { } } private static class DummyTypeResolverBuilder extends StdTypeResolverBuilder { } private static class DummyTypeIdResolver extends MinimalClassNameIdResolver { protected DummyTypeIdResolver() { super(SimpleType.constructUnsafe(Object.class), TypeFactory.defaultInstance(), BasicPolymorphicTypeValidator .builder().build()); } } }