From aa03d9a41c9b94cde74f038c210e2c2fccfbd1e8 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 23 May 2014 09:46:06 +0100 Subject: [PATCH] Make Health and Status immutable Update Health and Status objects to be immutable, update the existing builder methods to return new instances and add static convenience methods to Health. --- .../health/AbstractHealthAggregator.java | 21 +-- .../health/AbstractHealthIndicator.java | 12 +- .../boot/actuate/health/Health.java | 163 +++++++++++++----- .../actuate/health/MongoHealthIndicator.java | 4 +- .../health/OrderedHealthAggregator.java | 8 +- .../actuate/health/RabbitHealthIndicator.java | 24 +-- .../actuate/health/RedisHealthIndicator.java | 8 +- .../SimpleDataSourceHealthIndicator.java | 54 +++--- .../actuate/health/SolrHealthIndicator.java | 10 +- .../boot/actuate/health/Status.java | 2 +- .../health/VanillaHealthIndicator.java | 4 +- .../actuate/endpoint/HealthEndpointTests.java | 2 +- .../endpoint/mvc/HealthMvcEndpointTests.java | 6 +- .../health/CompositeHealthIndicatorTests.java | 22 +-- .../boot/actuate/health/HealthTests.java | 156 +++++++++++++++++ .../health/OrderedHealthAggregatorTests.java | 36 ++-- .../SimpleDataSourceHealthIndicatorTests.java | 4 +- 17 files changed, 381 insertions(+), 155 deletions(-) create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthTests.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthAggregator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthAggregator.java index 5074ab6691..d5c8f0b8e1 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthAggregator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthAggregator.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.health; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -31,21 +32,21 @@ public abstract class AbstractHealthAggregator implements HealthAggregator { @Override public final Health aggregate(Map healths) { - Health health = new Health(); - List status = new ArrayList(); + List statusCandidates = new ArrayList(); + Map details = new LinkedHashMap(); for (Map.Entry entry : healths.entrySet()) { - health.withDetail(entry.getKey(), entry.getValue()); - status.add(entry.getValue().getStatus()); + details.put(entry.getKey(), entry.getValue()); + statusCandidates.add(entry.getValue().getStatus()); } - health.status(aggregateStatus(status)); - return health; + return new Health(aggregateStatus(statusCandidates), details); } /** - * Actual aggregation logic. - * @param status list of given {@link Status} instances to aggregate - * @return aggregated {@link Status} + * Return the single 'aggregate' status that should be used from the specified + * candidates. + * @param candidates + * @return a single status */ - protected abstract Status aggregateStatus(List status); + protected abstract Status aggregateStatus(List candidates); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthIndicator.java index af0e27275d..04a874d93e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthIndicator.java @@ -21,7 +21,7 @@ package org.springframework.boot.actuate.health; * {@link Health} instance and error handling. *

* This implementation is only suitable if an {@link Exception} raised from - * {@link #doHealthCheck(Health)} should create a {@link Status#DOWN} health status. + * {@link #doHealthCheck()} should create a {@link Status#DOWN} health status. * * @author Christian Dupuis * @since 1.1.0 @@ -30,21 +30,19 @@ public abstract class AbstractHealthIndicator implements HealthIndicator { @Override public final Health health() { - Health health = new Health(); try { - doHealthCheck(health); + return doHealthCheck(); } catch (Exception ex) { - health.down().withException(ex); + return Health.down(ex); } - return health; } /** * Actual health check logic. - * @param health {@link Health} instance of report status. + * @return the {@link Health} * @throws Exception any {@link Exception} that should create a {@link Status#DOWN} * system status. */ - protected abstract void doHealthCheck(Health health) throws Exception; + protected abstract Health doHealthCheck() throws Exception; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java index 3013eddd32..558dd96e84 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java @@ -16,11 +16,11 @@ package org.springframework.boot.actuate.health; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -29,8 +29,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonUnwrapped; /** - * Value object used to carry information about the health information of a component or - * subsystem. + * Carries information about the health of a component or subsystem. *

* {@link Health} contains a {@link Status} to express the state of a component or * subsystem and some additional details to carry some contextual information. @@ -39,68 +38,52 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped; * in a {@link HealthIndicator} would be: * *

- * Health health = new Health();
  * try {
  * 	// do some test to determine state of component
- * 	health.up().withDetail("version", "1.1.2");
+ * 	return Health.up("version", "1.1.2");
  * }
  * catch (Exception ex) {
- * 	health.down().withException(ex);
+ * 	return Health.down(ex);
  * }
- * return health;
  * 
* * @author Christian Dupuis + * @author Phillip Webb * @since 1.1.0 */ @JsonInclude(Include.NON_EMPTY) -public class Health { +public final class Health { - private Status status; + private static final Map NO_DETAILS = Collections + . emptyMap(); - private Map details; + private final Status status; - public Health() { - this(Status.UNKNOWN); - } - - public Health(Status status) { - this.status = status; - this.details = new LinkedHashMap(); - } + private final Map details; - public Health status(Status status) { + /** + * Create a new {@link Health} instance with the specified status and details. + * @param status the status + * @param details the details or {@code null} + */ + public Health(Status status, Map details) { Assert.notNull(status, "Status must not be null"); this.status = status; - return this; - } - - public Health up() { - return status(Status.UP); - } - - public Health down() { - return status(Status.DOWN); - } - - public Health withException(Exception ex) { - Assert.notNull(ex, "Exception must not be null"); - return withDetail("error", ex.getClass().getName() + ": " + ex.getMessage()); - } - - @JsonAnySetter - public Health withDetail(String key, Object data) { - Assert.notNull(key, "Key must not be null"); - Assert.notNull(data, "Data must not be null"); - this.details.put(key, data); - return this; + this.details = Collections.unmodifiableMap(details == null ? NO_DETAILS + : new LinkedHashMap(details)); } + /** + * @return the status of the health (never {@code null}) + */ @JsonUnwrapped public Status getStatus() { return this.status; } + /** + * @return the details of the health or an empty map. + */ @JsonAnyGetter public Map getDetails() { return this.details; @@ -112,19 +95,105 @@ public class Health { return true; } if (obj != null && obj instanceof Health) { - return ObjectUtils.nullSafeEquals(this.status, ((Health) obj).status) - && ObjectUtils.nullSafeEquals(this.details, ((Health) obj).details); + Health other = (Health) obj; + return this.status.equals(other.status) && this.details.equals(other.details); } return false; } @Override public int hashCode() { - int hashCode = 0; - if (this.status != null) { - hashCode = this.status.hashCode(); - } + int hashCode = this.status.hashCode(); return 13 * hashCode + this.details.hashCode(); } + @Override + public String toString() { + return getStatus() + " " + getDetails(); + } + + /** + * Create a new {@link Health} object from this one, containing an additional + * exception detail. + * @param ex the exception + * @return a new {@link Health} instance + */ + public Health withException(Exception ex) { + Assert.notNull(ex, "Exception must not be null"); + return withDetail("error", ex.getClass().getName() + ": " + ex.getMessage()); + } + + /** + * Create a new {@link Health} object from this one, containing an additional detail. + * @param key the detail key + * @param data the detail data + * @return a new {@link Health} instance + */ + @JsonAnySetter + public Health withDetail(String key, Object data) { + Assert.notNull(key, "Key must not be null"); + Assert.notNull(data, "Data must not be null"); + Map details = new LinkedHashMap(this.details); + details.put(key, data); + return new Health(this.status, details); + } + + /** + * Create a new {@link Health} instance with an {@link Status#UNKNOWN} status. + * @return a new {@link Health} instance + */ + public static Health unknown() { + return status(Status.UNKNOWN); + } + + /** + * Create a new {@link Health} instance with an {@link Status#UP} status. + * @return a new {@link Health} instance + */ + public static Health up() { + return status(Status.UP); + } + + /** + * Create a new {@link Health} instance with an {@link Status#DOWN} status an the + * specified exception details. + * @param ex the exception + * @return a new {@link Health} instance + */ + public static Health down(Exception ex) { + return down().withException(ex); + } + + /** + * Create a new {@link Health} instance with a {@link Status#DOWN} status. + * @return a new {@link Health} instance + */ + public static Health down() { + return status(Status.DOWN); + } + + /** + * Create a new {@link Health} instance with an {@link Status#OUT_OF_SERVICE} status. + * @return a new {@link Health} instance + */ + public static Health outOfService() { + return status(Status.OUT_OF_SERVICE); + } + + /** + * Create a new {@link Health} instance with a specific status code. + * @return a new {@link Health} instance + */ + public static Health status(String statusCode) { + return status(new Status(statusCode)); + } + + /** + * Create a new {@link Health} instance with a specific {@link Status}. + * @return a new {@link Health} instance + */ + public static Health status(Status status) { + return new Health(status, null); + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/MongoHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/MongoHealthIndicator.java index c9d099d60c..a7840f84e2 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/MongoHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/MongoHealthIndicator.java @@ -38,9 +38,9 @@ public class MongoHealthIndicator extends AbstractHealthIndicator { } @Override - protected void doHealthCheck(Health health) throws Exception { + protected Health doHealthCheck() throws Exception { CommandResult result = this.mongoTemplate.executeCommand("{ serverStatus: 1 }"); - health.up().withDetail("version", result.getString("version")); + return Health.up().withDetail("version", result.getString("version")); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java index 38196deb7a..92a3a377c1 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java @@ -66,14 +66,14 @@ public class OrderedHealthAggregator extends AbstractHealthAggregator { } @Override - protected Status aggregateStatus(List status) { + protected Status aggregateStatus(List candidates) { // If no status is given return UNKNOWN - if (status.size() == 0) { + if (candidates.size() == 0) { return Status.UNKNOWN; } // Sort given Status instances by configured order - Collections.sort(status, new StatusComparator(this.statusOrder)); - return status.get(0); + Collections.sort(candidates, new StatusComparator(this.statusOrder)); + return candidates.get(0); } /** diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RabbitHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RabbitHealthIndicator.java index fd6a0efe93..567d967d53 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RabbitHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RabbitHealthIndicator.java @@ -41,17 +41,19 @@ public class RabbitHealthIndicator extends AbstractHealthIndicator { } @Override - protected void doHealthCheck(Health health) throws Exception { - health.up().withDetail("version", - this.rabbitTemplate.execute(new ChannelCallback() { - - @Override - public String doInRabbit(Channel channel) throws Exception { - Map serverProperties = channel.getConnection() - .getServerProperties(); - return serverProperties.get("version").toString(); - } - })); + protected Health doHealthCheck() throws Exception { + return Health.up().withDetail("version", getVersion()); + } + + private String getVersion() { + return this.rabbitTemplate.execute(new ChannelCallback() { + @Override + public String doInRabbit(Channel channel) throws Exception { + Map serverProperties = channel.getConnection() + .getServerProperties(); + return serverProperties.get("version").toString(); + } + }); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RedisHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RedisHealthIndicator.java index 731bcc500a..201d0d6f34 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RedisHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/RedisHealthIndicator.java @@ -40,12 +40,12 @@ public class RedisHealthIndicator extends AbstractHealthIndicator { } @Override - protected void doHealthCheck(Health health) throws Exception { - RedisConnection connection = null; + protected Health doHealthCheck() throws Exception { + RedisConnection connection = RedisConnectionUtils + .getConnection(this.redisConnectionFactory); try { - connection = RedisConnectionUtils.getConnection(this.redisConnectionFactory); Properties info = connection.info(); - health.up().withDetail("version", info.getProperty("redis_version")); + return Health.up().withDetail("version", info.getProperty("redis_version")); } finally { RedisConnectionUtils.releaseConnection(connection, diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicator.java index d61615a820..e21cadb53a 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicator.java @@ -35,7 +35,7 @@ import org.springframework.util.StringUtils; * @author Dave Syer * @author Christian Dupuis */ -public class SimpleDataSourceHealthIndicator implements HealthIndicator { +public class SimpleDataSourceHealthIndicator extends AbstractHealthIndicator { private DataSource dataSource; @@ -63,7 +63,6 @@ public class SimpleDataSourceHealthIndicator implements HealthIndicator { /** * Create a new {@link SimpleDataSourceHealthIndicator} using the specified * datasource. - * * @param dataSource the data source */ public SimpleDataSourceHealthIndicator(DataSource dataSource) { @@ -72,40 +71,39 @@ public class SimpleDataSourceHealthIndicator implements HealthIndicator { } @Override - public Health health() { - Health health = new Health(); - health.up(); + protected Health doHealthCheck() throws Exception { + if (this.dataSource == null) { + return Health.up().withDetail("database", "unknown"); + } + return doDataSourceHealthCheck(); + } - String product = "unknown"; - if (this.dataSource != null) { + private Health doDataSourceHealthCheck() throws Exception { + String product = getProduct(); + Health health = Health.up().withDetail("database", product); + String query = detectQuery(product); + if (StringUtils.hasText(query)) { try { - product = this.jdbcTemplate.execute(new ConnectionCallback() { - - @Override - public String doInConnection(Connection connection) - throws SQLException, DataAccessException { - return connection.getMetaData().getDatabaseProductName(); - } - }); - health.withDetail("database", product); - } - catch (DataAccessException ex) { - health.down().withException(ex); + health = health.withDetail("hello", + this.jdbcTemplate.queryForObject(query, Object.class)); } - String query = detectQuery(product); - if (StringUtils.hasText(query)) { - try { - health.withDetail("hello", - this.jdbcTemplate.queryForObject(query, Object.class)); - } - catch (Exception ex) { - health.down().withException(ex); - } + catch (Exception ex) { + return Health.down().withDetail("database", product).withException(ex); } } return health; } + private String getProduct() { + return this.jdbcTemplate.execute(new ConnectionCallback() { + @Override + public String doInConnection(Connection connection) throws SQLException, + DataAccessException { + return connection.getMetaData().getDatabaseProductName(); + } + }); + } + protected String detectQuery(String product) { String query = this.query; if (!StringUtils.hasText(query)) { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SolrHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SolrHealthIndicator.java index b989d2dde8..0098a50a08 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SolrHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SolrHealthIndicator.java @@ -20,7 +20,7 @@ import org.apache.solr.client.solrj.SolrServer; /** * {@link HealthIndicator} for Apache Solr - * + * * @author Andy Wilkinson * @since 1.1.0 */ @@ -33,9 +33,9 @@ public class SolrHealthIndicator extends AbstractHealthIndicator { } @Override - protected void doHealthCheck(Health health) throws Exception { - this.solrServer.ping(); - health.up().withDetail("solrStatus", - this.solrServer.ping().getResponse().get("status")); + protected Health doHealthCheck() throws Exception { + Object status = this.solrServer.ping().getResponse().get("status"); + return Health.up().withDetail("solrStatus", status); } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Status.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Status.java index 3c0ce292e1..516f9d8917 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Status.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Status.java @@ -35,7 +35,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; * @since 1.1.0 */ @JsonInclude(Include.NON_EMPTY) -public class Status { +public final class Status { /** * Convenient constant value representing unknown state diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/VanillaHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/VanillaHealthIndicator.java index b5a4f21d92..35862964b3 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/VanillaHealthIndicator.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/VanillaHealthIndicator.java @@ -25,8 +25,8 @@ package org.springframework.boot.actuate.health; public class VanillaHealthIndicator extends AbstractHealthIndicator { @Override - protected void doHealthCheck(Health health) throws Exception { - health.up(); + protected Health doHealthCheck() throws Exception { + return Health.up(); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java index 86678506d7..74ff29684a 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/HealthEndpointTests.java @@ -65,7 +65,7 @@ public class HealthEndpointTests extends AbstractEndpointTests { @Override public Health health() { - return new Health().status(new Status("FINE")); + return Health.status("FINE"); } }; } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpointTests.java index 80e4583b64..655f9fccc6 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HealthMvcEndpointTests.java @@ -51,7 +51,7 @@ public class HealthMvcEndpointTests { @Test public void up() { - when(this.endpoint.invoke()).thenReturn(new Health().up()); + when(this.endpoint.invoke()).thenReturn(Health.up()); Object result = this.mvc.invoke(); assertTrue(result instanceof Health); assertTrue(((Health) result).getStatus() == Status.UP); @@ -60,7 +60,7 @@ public class HealthMvcEndpointTests { @SuppressWarnings("unchecked") @Test public void down() { - when(this.endpoint.invoke()).thenReturn(new Health().down()); + when(this.endpoint.invoke()).thenReturn(Health.down()); Object result = this.mvc.invoke(); assertTrue(result instanceof ResponseEntity); ResponseEntity response = (ResponseEntity) result; @@ -71,7 +71,7 @@ public class HealthMvcEndpointTests { @SuppressWarnings("unchecked") @Test public void customMapping() { - when(this.endpoint.invoke()).thenReturn(new Health().status(new Status("OK"))); + when(this.endpoint.invoke()).thenReturn(Health.status("OK")); this.mvc.setStatusMapping(Collections.singletonMap("OK", HttpStatus.INTERNAL_SERVER_ERROR)); Object result = this.mvc.invoke(); diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java index 614c8d36ea..8df29d4079 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java @@ -34,7 +34,7 @@ import static org.mockito.BDDMockito.given; /** * Tests for {@link CompositeHealthIndicator} - * + * * @author Tyler J. Frederick * @author Phillip Webb * @author Christian Dupuis @@ -55,9 +55,9 @@ public class CompositeHealthIndicatorTests { @Before public void setup() { MockitoAnnotations.initMocks(this); - given(this.one.health()).willReturn(new Health().withDetail("1", "1")); - given(this.two.health()).willReturn(new Health().withDetail("2", "2")); - given(this.three.health()).willReturn(new Health().withDetail("3", "3")); + given(this.one.health()).willReturn(Health.unknown().withDetail("1", "1")); + given(this.two.health()).willReturn(Health.unknown().withDetail("2", "2")); + given(this.three.health()).willReturn(Health.unknown().withDetail("3", "3")); this.healthAggregator = new OrderedHealthAggregator(); } @@ -72,9 +72,9 @@ public class CompositeHealthIndicatorTests { Health result = composite.health(); assertThat(result.getDetails().size(), equalTo(2)); assertThat(result.getDetails(), - hasEntry("one", (Object) new Health().withDetail("1", "1"))); + hasEntry("one", (Object) Health.unknown().withDetail("1", "1"))); assertThat(result.getDetails(), - hasEntry("two", (Object) new Health().withDetail("2", "2"))); + hasEntry("two", (Object) Health.unknown().withDetail("2", "2"))); } @Test @@ -88,11 +88,11 @@ public class CompositeHealthIndicatorTests { Health result = composite.health(); assertThat(result.getDetails().size(), equalTo(3)); assertThat(result.getDetails(), - hasEntry("one", (Object) new Health().withDetail("1", "1"))); + hasEntry("one", (Object) Health.unknown().withDetail("1", "1"))); assertThat(result.getDetails(), - hasEntry("two", (Object) new Health().withDetail("2", "2"))); + hasEntry("two", (Object) Health.unknown().withDetail("2", "2"))); assertThat(result.getDetails(), - hasEntry("three", (Object) new Health().withDetail("3", "3"))); + hasEntry("three", (Object) Health.unknown().withDetail("3", "3"))); } @Test @@ -104,9 +104,9 @@ public class CompositeHealthIndicatorTests { Health result = composite.health(); assertThat(result.getDetails().size(), equalTo(2)); assertThat(result.getDetails(), - hasEntry("one", (Object) new Health().withDetail("1", "1"))); + hasEntry("one", (Object) Health.unknown().withDetail("1", "1"))); assertThat(result.getDetails(), - hasEntry("two", (Object) new Health().withDetail("2", "2"))); + hasEntry("two", (Object) Health.unknown().withDetail("2", "2"))); } @Test diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthTests.java new file mode 100644 index 0000000000..bfc44628ab --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2012-2014 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.health; + +import java.util.Collections; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link Health}. + * + * @author Phillip Webb + */ +public class HealthTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void statusMustNotBeNull() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Status must not be null"); + new Health(null, null); + } + + @Test + public void createWithStatus() throws Exception { + Health health = new Health(Status.UP, null); + assertThat(health.getStatus(), equalTo(Status.UP)); + assertThat(health.getDetails().size(), equalTo(0)); + } + + @Test + public void createWithDetails() throws Exception { + Health health = new Health(Status.UP, Collections.singletonMap("a", "b")); + assertThat(health.getStatus(), equalTo(Status.UP)); + assertThat(health.getDetails().get("a"), equalTo((Object) "b")); + } + + @Test + public void equalsAndHashCode() throws Exception { + Health h1 = new Health(Status.UP, Collections.singletonMap("a", "b")); + Health h2 = new Health(Status.UP, Collections.singletonMap("a", "b")); + Health h3 = new Health(Status.UP, null); + assertThat(h1, equalTo(h1)); + assertThat(h1, equalTo(h2)); + assertThat(h1, not(equalTo(h3))); + assertThat(h1.hashCode(), equalTo(h1.hashCode())); + assertThat(h1.hashCode(), equalTo(h2.hashCode())); + assertThat(h1.hashCode(), not(equalTo(h3.hashCode()))); + } + + @Test + public void withException() throws Exception { + RuntimeException ex = new RuntimeException("bang"); + Health health = new Health(Status.UP, Collections.singletonMap("a", "b")) + .withException(ex); + assertThat(health.getDetails().get("a"), equalTo((Object) "b")); + assertThat(health.getDetails().get("error"), + equalTo((Object) "java.lang.RuntimeException: bang")); + } + + @Test + public void withDetails() throws Exception { + Health health = new Health(Status.UP, Collections.singletonMap("a", "b")) + .withDetail("c", "d"); + assertThat(health.getDetails().get("a"), equalTo((Object) "b")); + assertThat(health.getDetails().get("c"), equalTo((Object) "d")); + } + + @Test + public void unknownWithDetails() throws Exception { + Health health = Health.unknown().withDetail("a", "b"); + assertThat(health.getStatus(), equalTo(Status.UNKNOWN)); + assertThat(health.getDetails().get("a"), equalTo((Object) "b")); + } + + @Test + public void unknown() throws Exception { + Health health = Health.unknown(); + assertThat(health.getStatus(), equalTo(Status.UNKNOWN)); + assertThat(health.getDetails().size(), equalTo(0)); + } + + @Test + public void upWithDetails() throws Exception { + Health health = Health.up().withDetail("a", "b"); + assertThat(health.getStatus(), equalTo(Status.UP)); + assertThat(health.getDetails().get("a"), equalTo((Object) "b")); + } + + @Test + public void up() throws Exception { + Health health = Health.up(); + assertThat(health.getStatus(), equalTo(Status.UP)); + assertThat(health.getDetails().size(), equalTo(0)); + } + + @Test + public void downWithException() throws Exception { + RuntimeException ex = new RuntimeException("bang"); + Health health = Health.down(ex); + assertThat(health.getStatus(), equalTo(Status.DOWN)); + assertThat(health.getDetails().get("error"), + equalTo((Object) "java.lang.RuntimeException: bang")); + } + + @Test + public void down() throws Exception { + Health health = Health.down(); + assertThat(health.getStatus(), equalTo(Status.DOWN)); + assertThat(health.getDetails().size(), equalTo(0)); + } + + @Test + public void outOfService() throws Exception { + Health health = Health.outOfService(); + assertThat(health.getStatus(), equalTo(Status.OUT_OF_SERVICE)); + assertThat(health.getDetails().size(), equalTo(0)); + } + + @Test + public void statusCode() throws Exception { + Health health = Health.status("UP"); + assertThat(health.getStatus(), equalTo(Status.UP)); + assertThat(health.getDetails().size(), equalTo(0)); + } + + @Test + public void status() throws Exception { + Health health = Health.status(Status.UP); + assertThat(health.getStatus(), equalTo(Status.UP)); + assertThat(health.getDetails().size(), equalTo(0)); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java index 9bfb110c82..8309aaba08 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java @@ -42,10 +42,10 @@ public class OrderedHealthAggregatorTests { @Test public void defaultOrder() { Map healths = new HashMap(); - healths.put("h1", new Health(Status.DOWN)); - healths.put("h2", new Health(Status.UP)); - healths.put("h3", new Health(Status.UNKNOWN)); - healths.put("h4", new Health(Status.OUT_OF_SERVICE)); + healths.put("h1", Health.status(Status.DOWN)); + healths.put("h2", Health.status(Status.UP)); + healths.put("h3", Health.status(Status.UNKNOWN)); + healths.put("h4", Health.status(Status.OUT_OF_SERVICE)); assertEquals(Status.DOWN, this.healthAggregator.aggregate(healths).getStatus()); } @@ -54,21 +54,21 @@ public class OrderedHealthAggregatorTests { this.healthAggregator.setStatusOrder(Status.UNKNOWN, Status.UP, Status.OUT_OF_SERVICE, Status.DOWN); Map healths = new HashMap(); - healths.put("h1", new Health(Status.DOWN)); - healths.put("h2", new Health(Status.UP)); - healths.put("h3", new Health(Status.UNKNOWN)); - healths.put("h4", new Health(Status.OUT_OF_SERVICE)); + healths.put("h1", Health.status(Status.DOWN)); + healths.put("h2", Health.status(Status.UP)); + healths.put("h3", Health.status(Status.UNKNOWN)); + healths.put("h4", Health.status(Status.OUT_OF_SERVICE)); assertEquals(Status.UNKNOWN, this.healthAggregator.aggregate(healths).getStatus()); } @Test public void defaultOrderWithCustomStatus() { Map healths = new HashMap(); - healths.put("h1", new Health(Status.DOWN)); - healths.put("h2", new Health(Status.UP)); - healths.put("h3", new Health(Status.UNKNOWN)); - healths.put("h4", new Health(Status.OUT_OF_SERVICE)); - healths.put("h5", new Health(new Status("CUSTOM"))); + healths.put("h1", Health.status(Status.DOWN)); + healths.put("h2", Health.status(Status.UP)); + healths.put("h3", Health.status(Status.UNKNOWN)); + healths.put("h4", Health.status(Status.OUT_OF_SERVICE)); + healths.put("h5", Health.status(new Status("CUSTOM"))); assertEquals(new Status("CUSTOM"), this.healthAggregator.aggregate(healths) .getStatus()); } @@ -78,11 +78,11 @@ public class OrderedHealthAggregatorTests { this.healthAggregator.setStatusOrder(Arrays.asList("DOWN", "OUT_OF_SERVICE", "UP", "UNKNOWN", "CUSTOM")); Map healths = new HashMap(); - healths.put("h1", new Health(Status.DOWN)); - healths.put("h2", new Health(Status.UP)); - healths.put("h3", new Health(Status.UNKNOWN)); - healths.put("h4", new Health(Status.OUT_OF_SERVICE)); - healths.put("h5", new Health(new Status("CUSTOM"))); + healths.put("h1", Health.status(Status.DOWN)); + healths.put("h2", Health.status(Status.UP)); + healths.put("h3", Health.status(Status.UNKNOWN)); + healths.put("h4", Health.status(Status.OUT_OF_SERVICE)); + healths.put("h5", Health.status(new Status("CUSTOM"))); assertEquals(Status.DOWN, this.healthAggregator.aggregate(healths).getStatus()); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicatorTests.java index 383de81285..e99dc0dd15 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicatorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleDataSourceHealthIndicatorTests.java @@ -27,8 +27,10 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.jdbc.datasource.SingleConnectionDataSource; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -78,7 +80,7 @@ public class SimpleDataSourceHealthIndicatorTests { this.indicator.setDataSource(this.dataSource); this.indicator.setQuery("SELECT COUNT(*) from BAR"); Health health = this.indicator.health(); - assertNotNull(health.getDetails().get("database")); + assertThat(health.getDetails().get("database"), notNullValue()); assertEquals(Status.DOWN, health.getStatus()); }