/* * 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.actuate.autoconfigure; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; import javax.sql.DataSource; import liquibase.integration.spring.SpringLiquibase; import org.flywaydb.core.Flyway; import org.junit.After; import org.junit.Test; import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint; import org.springframework.boot.actuate.endpoint.BeansEndpoint; import org.springframework.boot.actuate.endpoint.DumpEndpoint; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; import org.springframework.boot.actuate.endpoint.FlywayEndpoint; import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.InfoEndpoint; import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint; import org.springframework.boot.actuate.endpoint.LoggersEndpoint; import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.info.Info; import org.springframework.boot.actuate.info.InfoContributor; import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration; import org.springframework.boot.autoconfigure.info.ProjectInfoProperties; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; import org.springframework.boot.logging.LoggingSystem; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.Order; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.validation.BindException; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link EndpointAutoConfiguration}. * * @author Dave Syer * @author Phillip Webb * @author Greg Turnquist * @author Christian Dupuis * @author Stephane Nicoll * @author EddĂș MelĂ©ndez * @author Meang Akira Tanaka * @author Ben Hale */ public class EndpointAutoConfigurationTests { private AnnotationConfigApplicationContext context; @After public void close() { if (this.context != null) { this.context.close(); } } @Test public void endpoints() throws Exception { load(CustomLoggingConfig.class, EndpointAutoConfiguration.class); assertThat(this.context.getBean(BeansEndpoint.class)).isNotNull(); assertThat(this.context.getBean(DumpEndpoint.class)).isNotNull(); assertThat(this.context.getBean(EnvironmentEndpoint.class)).isNotNull(); assertThat(this.context.getBean(HealthEndpoint.class)).isNotNull(); assertThat(this.context.getBean(InfoEndpoint.class)).isNotNull(); assertThat(this.context.getBean(LoggersEndpoint.class)).isNotNull(); assertThat(this.context.getBean(MetricsEndpoint.class)).isNotNull(); assertThat(this.context.getBean(ShutdownEndpoint.class)).isNotNull(); assertThat(this.context.getBean(TraceEndpoint.class)).isNotNull(); assertThat(this.context.getBean(RequestMappingEndpoint.class)).isNotNull(); } @Test public void healthEndpoint() { load(EmbeddedDataSourceConfiguration.class, EndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class); HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); assertThat(bean).isNotNull(); Health result = bean.invoke(); assertThat(result).isNotNull(); assertThat(result.getDetails().containsKey("db")).isTrue(); } @Test public void healthEndpointWithDefaultHealthIndicator() { load(EndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class); HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); assertThat(bean).isNotNull(); Health result = bean.invoke(); assertThat(result).isNotNull(); } @Test public void loggersEndpointHasLoggers() throws Exception { load(CustomLoggingConfig.class, EndpointAutoConfiguration.class); LoggersEndpoint endpoint = this.context.getBean(LoggersEndpoint.class); Map<String, Object> result = endpoint.invoke(); assertThat((Map<?, ?>) result.get("loggers")).size().isGreaterThan(0); } @Test public void metricEndpointsHasSystemMetricsByDefault() { load(PublicMetricsAutoConfiguration.class, EndpointAutoConfiguration.class); MetricsEndpoint endpoint = this.context.getBean(MetricsEndpoint.class); Map<String, Object> metrics = endpoint.invoke(); assertThat(metrics.containsKey("mem")).isTrue(); assertThat(metrics.containsKey("heap.used")).isTrue(); } @Test public void metricEndpointCustomPublicMetrics() { load(CustomPublicMetricsConfig.class, PublicMetricsAutoConfiguration.class, EndpointAutoConfiguration.class); MetricsEndpoint endpoint = this.context.getBean(MetricsEndpoint.class); Map<String, Object> metrics = endpoint.invoke(); // Custom metrics assertThat(metrics.containsKey("foo")).isTrue(); // System metrics still available assertThat(metrics.containsKey("mem")).isTrue(); assertThat(metrics.containsKey("heap.used")).isTrue(); } @Test public void autoConfigurationAuditEndpoints() { load(EndpointAutoConfiguration.class, ConditionEvaluationReport.class); assertThat(this.context.getBean(AutoConfigurationReportEndpoint.class)) .isNotNull(); } @Test public void testInfoEndpoint() throws Exception { this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "info.foo:bar"); this.context.register(ProjectInfoAutoConfiguration.class, InfoContributorAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); assertThat(endpoint).isNotNull(); assertThat(endpoint.invoke().get("git")).isNotNull(); assertThat(endpoint.invoke().get("foo")).isEqualTo("bar"); } @Test public void testInfoEndpointNoGitProperties() throws Exception { this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "spring.info.git.location:classpath:nonexistent"); this.context.register(InfoContributorAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); assertThat(endpoint).isNotNull(); assertThat(endpoint.invoke().get("git")).isNull(); } @Test public void testInfoEndpointOrdering() throws Exception { this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "info.name:foo"); this.context.register(CustomInfoContributorsConfig.class, ProjectInfoAutoConfiguration.class, InfoContributorAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); Map<String, Object> info = endpoint.invoke(); assertThat(info).isNotNull(); assertThat(info.get("name")).isEqualTo("foo"); assertThat(info.get("version")).isEqualTo("1.0"); Object git = info.get("git"); assertThat(git).isInstanceOf(Map.class); } @Test public void testFlywayEndpoint() { this.context = new AnnotationConfigApplicationContext(); this.context.register(EmbeddedDataSourceConfiguration.class, FlywayAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); FlywayEndpoint endpoint = this.context.getBean(FlywayEndpoint.class); assertThat(endpoint).isNotNull(); assertThat(endpoint.invoke()).hasSize(1); } @Test public void testFlywayEndpointWithMultipleFlywayBeans() { this.context = new AnnotationConfigApplicationContext(); this.context.register(MultipleFlywayBeansConfig.class, FlywayAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBeansOfType(Flyway.class)).hasSize(2); assertThat(this.context.getBeansOfType(FlywayEndpoint.class)).hasSize(1); } @Test public void testLiquibaseEndpoint() { this.context = new AnnotationConfigApplicationContext(); this.context.register(EmbeddedDataSourceConfiguration.class, LiquibaseAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); LiquibaseEndpoint endpoint = this.context.getBean(LiquibaseEndpoint.class); assertThat(endpoint).isNotNull(); assertThat(endpoint.invoke()).hasSize(1); } @Test public void testLiquibaseEndpointWithMultipleSpringLiquibaseBeans() { this.context = new AnnotationConfigApplicationContext(); this.context.register(MultipleLiquibaseBeansConfig.class, LiquibaseAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBeansOfType(SpringLiquibase.class)).hasSize(2); assertThat(this.context.getBeansOfType(LiquibaseEndpoint.class)).hasSize(1); } private void load(Class<?>... config) { this.context = new AnnotationConfigApplicationContext(); this.context.register(config); this.context.refresh(); } @Configuration static class CustomLoggingConfig { @Bean LoggingSystem loggingSystem() { return LoggingSystem.get(getClass().getClassLoader()); } } @Configuration static class CustomPublicMetricsConfig { @Bean PublicMetrics customPublicMetrics() { return new PublicMetrics() { @Override public Collection<Metric<?>> metrics() { Metric<Integer> metric = new Metric<>("foo", 1); return Collections.<Metric<?>>singleton(metric); } }; } } @Configuration static class CustomInfoContributorsConfig { @Bean @Order(InfoContributorAutoConfiguration.DEFAULT_ORDER - 1) public InfoContributor myInfoContributor() { return new InfoContributor() { @Override public void contribute(Info.Builder builder) { builder.withDetail("name", "bar"); builder.withDetail("version", "1.0"); } }; } @Bean @Order(InfoContributorAutoConfiguration.DEFAULT_ORDER + 1) public InfoContributor myAnotherContributor(ProjectInfoProperties properties) throws IOException, BindException { return new GitFullInfoContributor(properties.getGit().getLocation()); } private static class GitFullInfoContributor implements InfoContributor { private static final ResolvableType STRING_OBJECT_MAP = ResolvableType .forClassWithGenerics(Map.class, String.class, Object.class); private Map<String, Object> content = new LinkedHashMap<>(); GitFullInfoContributor(Resource location) throws BindException, IOException { if (!location.exists()) { return; } Properties properties = PropertiesLoaderUtils.loadProperties(location); new Binder(new MapConfigurationPropertySource(properties)).bind("info", Bindable.of(STRING_OBJECT_MAP).withExistingValue(this.content)); } @Override public void contribute(Info.Builder builder) { if (!this.content.isEmpty()) { builder.withDetail("git", this.content); } } } } static class DataSourceConfig { @Bean public DataSource dataSourceOne() { return DataSourceBuilder.create().url("jdbc:hsqldb:mem:changelogdbtest") .username("sa").build(); } @Bean public DataSource dataSourceTwo() { return DataSourceBuilder.create().url("jdbc:hsqldb:mem:changelogdbtest2") .username("sa").build(); } } @Configuration static class MultipleFlywayBeansConfig extends DataSourceConfig { @Bean public Flyway flywayOne() { Flyway flyway = new Flyway(); flyway.setDataSource(dataSourceOne()); return flyway; } @Bean public Flyway flywayTwo() { Flyway flyway = new Flyway(); flyway.setDataSource(dataSourceTwo()); return flyway; } } @Configuration static class MultipleLiquibaseBeansConfig extends DataSourceConfig { @Bean public SpringLiquibase liquibaseOne() { SpringLiquibase liquibase = new SpringLiquibase(); liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); liquibase.setDataSource(dataSourceOne()); return liquibase; } @Bean public SpringLiquibase liquibaseTwo() { SpringLiquibase liquibase = new SpringLiquibase(); liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); liquibase.setDataSource(dataSourceTwo()); return liquibase; } } }