From eda14fb0f67665ca66cd7bb4375138255e3cd97a Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Thu, 18 Apr 2019 11:42:08 -0700 Subject: [PATCH] Analyze failure if configprop scanning results in two beans Closes gh-16581 --- .../ConfigurationPropertiesScanRegistrar.java | 17 +++++ ...validConfigurationPropertiesException.java | 50 ++++++++++++++ ...onfigurationPropertiesFailureAnalyzer.java | 52 +++++++++++++++ .../main/resources/META-INF/spring.factories | 1 + ...igurationPropertiesScanRegistrarTests.java | 53 ++++++++++++--- .../scan/invalid/c/InvalidConfiguration.java | 32 +++++++++ .../invalid/d/OtherInvalidConfiguration.java | 33 ++++++++++ ...figurationPropertiesScanConfiguration.java | 6 +- .../{ => valid}/a/AScanConfiguration.java | 2 +- .../{ => valid}/b/BScanConfiguration.java | 2 +- ...urationPropertiesFailureAnalyzerTests.java | 66 +++++++++++++++++++ 11 files changed, 301 insertions(+), 13 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/InvalidConfigurationPropertiesException.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertiesFailureAnalyzer.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/invalid/c/InvalidConfiguration.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/invalid/d/OtherInvalidConfiguration.java rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/{ => valid}/ConfigurationPropertiesScanConfiguration.java (91%) rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/{ => valid}/a/AScanConfiguration.java (92%) rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/{ => valid}/b/BScanConfiguration.java (92%) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertiesFailureAnalyzerTests.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.java index ebae95f75f..94c6fbe461 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.java @@ -25,8 +25,11 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -85,6 +88,7 @@ class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegist String beanClassName = candidate.getBeanClassName(); try { Class type = ClassUtils.forName(beanClassName, null); + validateScanConfiguration(type); ConfigurationPropertiesBeanDefinitionRegistrar.register(registry, beanFactory, type); } @@ -94,4 +98,17 @@ class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegist } } + private void validateScanConfiguration(Class type) { + MergedAnnotation component = MergedAnnotations + .from(type, MergedAnnotations.SearchStrategy.EXHAUSTIVE) + .get(Component.class); + if (component.isPresent()) { + MergedAnnotation parent = component; + while (parent.getParent() != null) { + parent = parent.getParent(); + } + throw new InvalidConfigurationPropertiesException(type, parent.getType()); + } + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/InvalidConfigurationPropertiesException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/InvalidConfigurationPropertiesException.java new file mode 100644 index 0000000000..9c54d5a43d --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/InvalidConfigurationPropertiesException.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.context.properties; + +import org.springframework.util.Assert; + +/** + * Exception thrown when a {@link ConfigurationProperties} has been misconfigured. + * + * @author Madhura Bhave + * @since 2.2.0 + */ +public class InvalidConfigurationPropertiesException extends RuntimeException { + + private final Class configurationProperties; + + private final Class component; + + public InvalidConfigurationPropertiesException(Class configurationProperties, + Class component) { + super("Found @" + component.getSimpleName() + " and @ConfigurationProperties on " + + configurationProperties.getName() + "."); + Assert.notNull(configurationProperties, "Class must not be null"); + this.configurationProperties = configurationProperties; + this.component = component; + } + + public Class getConfigurationProperties() { + return this.configurationProperties; + } + + public Class getComponent() { + return this.component; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertiesFailureAnalyzer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertiesFailureAnalyzer.java new file mode 100644 index 0000000000..ba210b5e36 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertiesFailureAnalyzer.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.diagnostics.analyzer; + +import org.springframework.boot.context.properties.InvalidConfigurationPropertiesException; +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; + +/** + * An {@link AbstractFailureAnalyzer} that performs analysis of failures caused by + * {@link InvalidConfigurationPropertiesException}. + * + * @author Madhura Bhave + */ +public class InvalidConfigurationPropertiesFailureAnalyzer + extends AbstractFailureAnalyzer { + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, + InvalidConfigurationPropertiesException cause) { + Class configurationProperties = cause.getConfigurationProperties(); + String component = cause.getComponent().getSimpleName(); + return new FailureAnalysis(getDescription(configurationProperties, component), + getAction(configurationProperties, component), cause); + } + + private String getDescription(Class configurationProperties, String component) { + return configurationProperties.getName() + + " is annotated with @ConfigurationProperties and @" + component + + ". This may cause the @ConfigurationProperties bean to be registered twice."; + } + + private String getAction(Class configurationProperties, String component) { + return "Remove either @ConfigurationProperties or @" + component + " from " + + configurationProperties; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories index 0e09bea33e..5312bed388 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories @@ -49,6 +49,7 @@ org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\ + org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertiesFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrarTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrarTests.java index 2e2d1f29d3..eae0e120bd 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrarTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrarTests.java @@ -22,11 +22,14 @@ import org.junit.Test; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration; +import org.springframework.boot.context.properties.scan.invalid.c.InvalidConfiguration; +import org.springframework.boot.context.properties.scan.invalid.d.OtherInvalidConfiguration; +import org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link ConfigurationPropertiesScanRegistrar}. @@ -46,11 +49,11 @@ public class ConfigurationPropertiesScanRegistrarTests { getAnnotationMetadata(ConfigurationPropertiesScanConfiguration.class), this.beanFactory); BeanDefinition bingDefinition = this.beanFactory.getBeanDefinition( - "bing-org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration$BingProperties"); + "bing-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$BingProperties"); BeanDefinition fooDefinition = this.beanFactory.getBeanDefinition( - "foo-org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration$FooProperties"); + "foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties"); BeanDefinition barDefinition = this.beanFactory.getBeanDefinition( - "bar-org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration$BarProperties"); + "bar-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$BarProperties"); assertThat(bingDefinition).isExactlyInstanceOf(GenericBeanDefinition.class); assertThat(fooDefinition).isExactlyInstanceOf(GenericBeanDefinition.class); assertThat(barDefinition) @@ -66,7 +69,7 @@ public class ConfigurationPropertiesScanRegistrarTests { ConfigurationPropertiesScanConfiguration.TestConfiguration.class), beanFactory); BeanDefinition fooDefinition = beanFactory.getBeanDefinition( - "foo-org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration$FooProperties"); + "foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties"); assertThat(fooDefinition).isExactlyInstanceOf(GenericBeanDefinition.class); } @@ -79,19 +82,53 @@ public class ConfigurationPropertiesScanRegistrarTests { ConfigurationPropertiesScanConfiguration.DifferentPackageConfiguration.class), beanFactory); assertThat(beanFactory.containsBeanDefinition( - "foo-org.springframework.boot.context.properties.scan.ConfigurationPropertiesScanConfiguration$FooProperties")) + "foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties")) .isFalse(); BeanDefinition aDefinition = beanFactory.getBeanDefinition( - "a-org.springframework.boot.context.properties.scan.a.AScanConfiguration$AProperties"); + "a-org.springframework.boot.context.properties.scan.valid.a.AScanConfiguration$AProperties"); BeanDefinition bDefinition = beanFactory.getBeanDefinition( - "b-org.springframework.boot.context.properties.scan.b.BScanConfiguration$BProperties"); + "b-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BProperties"); assertThat(aDefinition).isExactlyInstanceOf(GenericBeanDefinition.class); assertThat(bDefinition).isExactlyInstanceOf(GenericBeanDefinition.class); } + @Test + public void scanWhenComponentAnnotationPresentShouldThrowException() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.setAllowBeanDefinitionOverriding(false); + assertThatExceptionOfType(InvalidConfigurationPropertiesException.class) + .isThrownBy(() -> this.registrar.registerBeanDefinitions( + getAnnotationMetadata(InvalidScanConfiguration.class), + beanFactory)) + .withMessageContaining( + "Found @Component and @ConfigurationProperties on org.springframework.boot.context.properties.scan.invalid.c.InvalidConfiguration$MyProperties."); + } + + @Test + public void scanWhenOtherComponentAnnotationPresentShouldThrowException() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.setAllowBeanDefinitionOverriding(false); + assertThatExceptionOfType(InvalidConfigurationPropertiesException.class) + .isThrownBy(() -> this.registrar.registerBeanDefinitions( + getAnnotationMetadata(OtherInvalidScanConfiguration.class), + beanFactory)) + .withMessageContaining( + "Found @RestController and @ConfigurationProperties on org.springframework.boot.context.properties.scan.invalid.d.OtherInvalidConfiguration$MyControllerProperties."); + } + private AnnotationMetadata getAnnotationMetadata(Class source) throws IOException { return new SimpleMetadataReaderFactory().getMetadataReader(source.getName()) .getAnnotationMetadata(); } + @ConfigurationPropertiesScan(basePackageClasses = InvalidConfiguration.class) + static class InvalidScanConfiguration { + + } + + @ConfigurationPropertiesScan(basePackageClasses = OtherInvalidConfiguration.class) + static class OtherInvalidScanConfiguration { + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/invalid/c/InvalidConfiguration.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/invalid/c/InvalidConfiguration.java new file mode 100644 index 0000000000..b04f9cba91 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/invalid/c/InvalidConfiguration.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.context.properties.scan.invalid.c; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author Madhura Bhave + */ +public class InvalidConfiguration { + + @Component + @ConfigurationProperties(prefix = "b") + static class MyProperties { + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/invalid/d/OtherInvalidConfiguration.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/invalid/d/OtherInvalidConfiguration.java new file mode 100644 index 0000000000..af63828703 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/invalid/d/OtherInvalidConfiguration.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.context.properties.scan.invalid.d; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Madhura Bhave + */ +public class OtherInvalidConfiguration { + + @RestController + @ConfigurationProperties(prefix = "c") + static class MyControllerProperties { + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/ConfigurationPropertiesScanConfiguration.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/valid/ConfigurationPropertiesScanConfiguration.java similarity index 91% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/ConfigurationPropertiesScanConfiguration.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/valid/ConfigurationPropertiesScanConfiguration.java index 88111515f2..06da868fe1 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/ConfigurationPropertiesScanConfiguration.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/valid/ConfigurationPropertiesScanConfiguration.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.boot.context.properties.scan; +package org.springframework.boot.context.properties.scan.valid; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.scan.b.BScanConfiguration; +import org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration; /** * Used for testing {@link ConfigurationProperties} scanning. @@ -36,7 +36,7 @@ public class ConfigurationPropertiesScanConfiguration { } @ConfigurationPropertiesScan( - basePackages = "org.springframework.boot.context.properties.scan.a", + basePackages = "org.springframework.boot.context.properties.scan.valid.a", basePackageClasses = BScanConfiguration.class) public static class DifferentPackageConfiguration { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/a/AScanConfiguration.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/valid/a/AScanConfiguration.java similarity index 92% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/a/AScanConfiguration.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/valid/a/AScanConfiguration.java index 02a662878e..f798ed4542 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/a/AScanConfiguration.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/valid/a/AScanConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.boot.context.properties.scan.a; +package org.springframework.boot.context.properties.scan.valid.a; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/b/BScanConfiguration.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/valid/b/BScanConfiguration.java similarity index 92% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/b/BScanConfiguration.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/valid/b/BScanConfiguration.java index 81b924bf30..98e25c9d22 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/b/BScanConfiguration.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/scan/valid/b/BScanConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.boot.context.properties.scan.b; +package org.springframework.boot.context.properties.scan.valid.b; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertiesFailureAnalyzerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertiesFailureAnalyzerTests.java new file mode 100644 index 0000000000..463b93721c --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertiesFailureAnalyzerTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.diagnostics.analyzer; + +import org.junit.Test; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.InvalidConfigurationPropertiesException; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.stereotype.Component; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link InvalidConfigurationPropertiesFailureAnalyzer} + * + * @author Madhura Bhave + */ +public class InvalidConfigurationPropertiesFailureAnalyzerTests { + + private final InvalidConfigurationPropertiesFailureAnalyzer analyzer = new InvalidConfigurationPropertiesFailureAnalyzer(); + + @Test + public void analysisForInvalidConfigurationOfConfigurationProperties() { + FailureAnalysis analysis = performAnalysis(); + assertThat(analysis.getDescription()).isEqualTo(getDescription()); + assertThat(analysis.getAction()) + .isEqualTo("Remove either @ConfigurationProperties or @Component from " + + TestProperties.class); + } + + private String getDescription() { + return TestProperties.class.getName() + + " is annotated with @ConfigurationProperties and @Component" + + ". This may cause the @ConfigurationProperties bean to be registered twice."; + } + + private FailureAnalysis performAnalysis() { + FailureAnalysis analysis = this.analyzer + .analyze(new InvalidConfigurationPropertiesException(TestProperties.class, + Component.class)); + assertThat(analysis).isNotNull(); + return analysis; + } + + @ConfigurationProperties + @Component + private static class TestProperties { + + } + +}