/* * Copyright 2012-2017 the original author or authors. * * 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 org.springframework.boot.autoconfigure.web.reactive; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; import javax.validation.ValidatorFactory; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.DirectFieldAccessor; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationTests.Config; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.io.ClassPathResource; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.ObjectUtils; import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.accept.CompositeContentTypeResolver; import org.springframework.web.reactive.config.WebFluxConfigurationSupport; import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; import org.springframework.web.reactive.resource.CachingResourceResolver; import org.springframework.web.reactive.resource.CachingResourceTransformer; import org.springframework.web.reactive.resource.PathResourceResolver; import org.springframework.web.reactive.resource.ResourceWebHandler; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; import org.springframework.web.reactive.result.view.ViewResolver; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; /** * Tests for {@link WebFluxAutoConfiguration}. * * @author Brian Clozel * @author Andy Wilkinson */ public class WebFluxAutoConfigurationTests { @Rule public ExpectedException thrown = ExpectedException.none(); private GenericReactiveWebApplicationContext context; @Test public void shouldNotProcessIfExistingWebReactiveConfiguration() throws Exception { load(WebFluxConfigurationSupport.class); assertThat(this.context.getBeansOfType(RequestMappingHandlerMapping.class).size()) .isEqualTo(1); assertThat(this.context.getBeansOfType(RequestMappingHandlerAdapter.class).size()) .isEqualTo(1); } @Test public void shouldCreateDefaultBeans() throws Exception { load(); assertThat(this.context.getBeansOfType(RequestMappingHandlerMapping.class).size()) .isEqualTo(1); assertThat(this.context.getBeansOfType(RequestMappingHandlerAdapter.class).size()) .isEqualTo(1); assertThat(this.context.getBeansOfType(CompositeContentTypeResolver.class).size()) .isEqualTo(1); assertThat(this.context.getBean("resourceHandlerMapping", HandlerMapping.class)) .isNotNull(); } @SuppressWarnings("unchecked") @Test public void shouldRegisterCustomHandlerMethodArgumentResolver() throws Exception { load(CustomArgumentResolvers.class); RequestMappingHandlerAdapter adapter = this.context .getBean(RequestMappingHandlerAdapter.class); assertThat((List<HandlerMethodArgumentResolver>) ReflectionTestUtils .getField(adapter.getArgumentResolverConfigurer(), "customResolvers")) .contains( this.context.getBean("firstResolver", HandlerMethodArgumentResolver.class), this.context.getBean("secondResolver", HandlerMethodArgumentResolver.class)); } @Test public void shouldRegisterResourceHandlerMapping() throws Exception { load(); SimpleUrlHandlerMapping hm = this.context.getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class); assertThat(hm.getUrlMap().get("/**")).isInstanceOf(ResourceWebHandler.class); ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/**"); assertThat(staticHandler.getLocations()).hasSize(5); assertThat(hm.getUrlMap().get("/webjars/**")) .isInstanceOf(ResourceWebHandler.class); ResourceWebHandler webjarsHandler = (ResourceWebHandler) hm.getUrlMap() .get("/webjars/**"); assertThat(webjarsHandler.getLocations()).hasSize(1); assertThat(webjarsHandler.getLocations().get(0)) .isEqualTo(new ClassPathResource("/META-INF/resources/webjars/")); } @Test public void shouldMapResourcesToCustomPath() throws Exception { load("spring.webflux.static-path-pattern:/static/**"); SimpleUrlHandlerMapping hm = this.context.getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class); assertThat(hm.getUrlMap().get("/static/**")) .isInstanceOf(ResourceWebHandler.class); ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap() .get("/static/**"); assertThat(staticHandler.getLocations()).hasSize(5); } @Test public void shouldNotMapResourcesWhenDisabled() throws Exception { load("spring.resources.add-mappings:false"); assertThat(this.context.getBean("resourceHandlerMapping")) .isNotInstanceOf(SimpleUrlHandlerMapping.class); } @Test public void resourceHandlerChainEnabled() throws Exception { load("spring.resources.chain.enabled:true"); SimpleUrlHandlerMapping hm = this.context.getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class); assertThat(hm.getUrlMap().get("/**")).isInstanceOf(ResourceWebHandler.class); ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/**"); assertThat(staticHandler.getResourceResolvers()).extractingResultOf("getClass") .containsOnly(CachingResourceResolver.class, PathResourceResolver.class); assertThat(staticHandler.getResourceTransformers()).extractingResultOf("getClass") .containsOnly(CachingResourceTransformer.class); } @Test public void shouldRegisterViewResolvers() throws Exception { load(ViewResolvers.class); ViewResolutionResultHandler resultHandler = this.context .getBean(ViewResolutionResultHandler.class); assertThat(resultHandler.getViewResolvers()).containsExactly( this.context.getBean("aViewResolver", ViewResolver.class), this.context.getBean("anotherViewResolver", ViewResolver.class)); } @Test public void validatorWhenNoValidatorShouldUseDefault() { load(null, new Class<?>[] { ValidationAutoConfiguration.class }); assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) .isEmpty(); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(springValidatorBeans).containsExactly("webFluxValidator"); } @Test public void validatorWhenNoCustomizationShouldUseAutoConfigured() { load(); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(jsrValidatorBeans).containsExactly("defaultValidator"); assertThat(springValidatorBeans).containsExactly("defaultValidator", "webFluxValidator"); Validator validator = this.context.getBean("webFluxValidator", Validator.class); assertThat(validator).isInstanceOf(ValidatorAdapter.class); Object defaultValidator = this.context.getBean("defaultValidator"); assertThat(((ValidatorAdapter) validator).getTarget()).isSameAs(defaultValidator); // Primary Spring validator is the one use by WebFlux behind the scenes assertThat(this.context.getBean(Validator.class)).isEqualTo(defaultValidator); } @Test public void validatorWithConfigurerShouldUseSpringValidator() { load(ValidatorWebFluxConfigurer.class, new Class<?>[] { ValidationAutoConfiguration.class }); assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) .isEmpty(); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(springValidatorBeans).containsExactly("webFluxValidator"); assertThat(this.context.getBean("webFluxValidator")) .isSameAs(this.context.getBean(ValidatorWebFluxConfigurer.class).validator); } @Test public void validatorWithConfigurerDoesNotExposeJsr303() { load(ValidatorJsr303WebFluxConfigurer.class, new Class<?>[] { ValidationAutoConfiguration.class }); assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) .isEmpty(); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(springValidatorBeans).containsExactly("webFluxValidator"); Validator validator = this.context.getBean("webFluxValidator", Validator.class); assertThat(validator).isInstanceOf(ValidatorAdapter.class); assertThat(((ValidatorAdapter) validator).getTarget()) .isSameAs(this.context.getBean(ValidatorJsr303WebFluxConfigurer.class).validator); } @Test public void validationCustomConfigurerTakesPrecedence() { load(ValidatorWebFluxConfigurer.class); assertThat(this.context.getBeansOfType(ValidatorFactory.class)).hasSize(1); assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) .hasSize(1); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(springValidatorBeans) .containsExactly("defaultValidator", "webFluxValidator"); assertThat(this.context.getBean("webFluxValidator")) .isSameAs(this.context.getBean(ValidatorWebFluxConfigurer.class).validator); // Primary Spring validator is the auto-configured one as the WebFlux one has been // customized via a WebFluxConfigurer assertThat(this.context.getBean(Validator.class)) .isEqualTo(this.context.getBean("defaultValidator")); } @Test public void validatorWithCustomSpringValidatorIgnored() { load(CustomSpringValidator.class); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(jsrValidatorBeans).containsExactly("defaultValidator"); assertThat(springValidatorBeans).containsExactly( "customValidator", "defaultValidator", "webFluxValidator"); Validator validator = this.context.getBean("webFluxValidator", Validator.class); assertThat(validator).isInstanceOf(ValidatorAdapter.class); Object defaultValidator = this.context.getBean("defaultValidator"); assertThat(((ValidatorAdapter) validator).getTarget()) .isSameAs(defaultValidator); // Primary Spring validator is the one use by WebFlux behind the scenes assertThat(this.context.getBean(Validator.class)).isEqualTo(defaultValidator); } @Test public void validatorWithCustomJsr303ValidatorExposedAsSpringValidator() { load(CustomJsr303Validator.class); assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(jsrValidatorBeans).containsExactly("customValidator"); assertThat(springValidatorBeans).containsExactly("webFluxValidator"); Validator validator = this.context.getBean(Validator.class); assertThat(validator).isInstanceOf(ValidatorAdapter.class); Validator target = ((ValidatorAdapter) validator).getTarget(); assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator")) .isSameAs(this.context.getBean("customValidator")); } private void load(String... environment) { load(null, environment); } private void load(Class<?> config, String... environment) { load(config, null, environment); } private void load(Class<?> config, Class<?>[] exclude, String... environment) { this.context = new GenericReactiveWebApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, environment); List<Class<?>> configClasses = new ArrayList<>(); if (config != null) { configClasses.add(config); } configClasses.addAll(Arrays.asList(Config.class, ValidationAutoConfiguration.class, BaseConfiguration.class)); if (!ObjectUtils.isEmpty(exclude)) { configClasses.removeAll(Arrays.asList(exclude)); } this.context.register(configClasses.toArray(new Class<?>[configClasses.size()])); this.context.refresh(); } @Configuration protected static class CustomArgumentResolvers { @Bean public HandlerMethodArgumentResolver firstResolver() { return mock(HandlerMethodArgumentResolver.class); } @Bean public HandlerMethodArgumentResolver secondResolver() { return mock(HandlerMethodArgumentResolver.class); } } @Configuration protected static class ViewResolvers { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public ViewResolver aViewResolver() { return mock(ViewResolver.class); } @Bean public ViewResolver anotherViewResolver() { return mock(ViewResolver.class); } } @Configuration @Import({ WebFluxAutoConfiguration.class }) @EnableConfigurationProperties(WebFluxProperties.class) protected static class BaseConfiguration { @Bean public MockReactiveWebServerFactory mockReactiveWebServerFactory() { return new MockReactiveWebServerFactory(); } } @Configuration protected static class CustomHttpHandler { @Bean public HttpHandler httpHandler() { return (serverHttpRequest, serverHttpResponse) -> null; } } @Configuration protected static class ValidatorWebFluxConfigurer implements WebFluxConfigurer { private final Validator validator = mock(Validator.class); @Override public Optional<Validator> getValidator() { return Optional.of(this.validator); } } @Configuration protected static class ValidatorJsr303WebFluxConfigurer implements WebFluxConfigurer { private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); @Override public Optional<Validator> getValidator() { return Optional.of(this.validator); } } @Configuration static class CustomJsr303Validator { @Bean public javax.validation.Validator customValidator() { return mock(javax.validation.Validator.class); } } @Configuration static class CustomSpringValidator { @Bean public Validator customValidator() { return mock(Validator.class); } } }