Commit aa03d9a4 authored by Phillip Webb's avatar Phillip Webb

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.
parent 660d9e24
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -31,21 +32,21 @@ public abstract class AbstractHealthAggregator implements HealthAggregator { ...@@ -31,21 +32,21 @@ public abstract class AbstractHealthAggregator implements HealthAggregator {
@Override @Override
public final Health aggregate(Map<String, Health> healths) { public final Health aggregate(Map<String, Health> healths) {
Health health = new Health(); List<Status> statusCandidates = new ArrayList<Status>();
List<Status> status = new ArrayList<Status>(); Map<String, Object> details = new LinkedHashMap<String, Object>();
for (Map.Entry<String, Health> entry : healths.entrySet()) { for (Map.Entry<String, Health> entry : healths.entrySet()) {
health.withDetail(entry.getKey(), entry.getValue()); details.put(entry.getKey(), entry.getValue());
status.add(entry.getValue().getStatus()); statusCandidates.add(entry.getValue().getStatus());
} }
health.status(aggregateStatus(status)); return new Health(aggregateStatus(statusCandidates), details);
return health;
} }
/** /**
* Actual aggregation logic. * Return the single 'aggregate' status that should be used from the specified
* @param status list of given {@link Status} instances to aggregate * candidates.
* @return aggregated {@link Status} * @param candidates
* @return a single status
*/ */
protected abstract Status aggregateStatus(List<Status> status); protected abstract Status aggregateStatus(List<Status> candidates);
} }
...@@ -21,7 +21,7 @@ package org.springframework.boot.actuate.health; ...@@ -21,7 +21,7 @@ package org.springframework.boot.actuate.health;
* {@link Health} instance and error handling. * {@link Health} instance and error handling.
* <p> * <p>
* This implementation is only suitable if an {@link Exception} raised from * 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 * @author Christian Dupuis
* @since 1.1.0 * @since 1.1.0
...@@ -30,21 +30,19 @@ public abstract class AbstractHealthIndicator implements HealthIndicator { ...@@ -30,21 +30,19 @@ public abstract class AbstractHealthIndicator implements HealthIndicator {
@Override @Override
public final Health health() { public final Health health() {
Health health = new Health();
try { try {
doHealthCheck(health); return doHealthCheck();
} }
catch (Exception ex) { catch (Exception ex) {
health.down().withException(ex); return Health.down(ex);
} }
return health;
} }
/** /**
* Actual health check logic. * 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} * @throws Exception any {@link Exception} that should create a {@link Status#DOWN}
* system status. * system status.
*/ */
protected abstract void doHealthCheck(Health health) throws Exception; protected abstract Health doHealthCheck() throws Exception;
} }
...@@ -16,11 +16,11 @@ ...@@ -16,11 +16,11 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonAnySetter;
...@@ -29,8 +29,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; ...@@ -29,8 +29,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.annotation.JsonUnwrapped;
/** /**
* Value object used to carry information about the health information of a component or * Carries information about the health of a component or subsystem.
* subsystem.
* <p> * <p>
* {@link Health} contains a {@link Status} to express the state of a component or * {@link Health} contains a {@link Status} to express the state of a component or
* subsystem and some additional details to carry some contextual information. * subsystem and some additional details to carry some contextual information.
...@@ -39,92 +38,162 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped; ...@@ -39,92 +38,162 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped;
* in a {@link HealthIndicator} would be: * in a {@link HealthIndicator} would be:
* *
* <pre class="code"> * <pre class="code">
* Health health = new Health();
* try { * try {
* // do some test to determine state of component * // do some test to determine state of component
* health.up().withDetail(&quot;version&quot;, &quot;1.1.2&quot;); * return Health.up(&quot;version&quot;, &quot;1.1.2&quot;);
* } * }
* catch (Exception ex) { * catch (Exception ex) {
* health.down().withException(ex); * return Health.down(ex);
* } * }
* return health;
* </pre> * </pre>
* *
* @author Christian Dupuis * @author Christian Dupuis
* @author Phillip Webb
* @since 1.1.0 * @since 1.1.0
*/ */
@JsonInclude(Include.NON_EMPTY) @JsonInclude(Include.NON_EMPTY)
public class Health { public final class Health {
private Status status; private static final Map<String, Object> NO_DETAILS = Collections
.<String, Object> emptyMap();
private Map<String, Object> details; private final Status status;
public Health() { private final Map<String, Object> details;
this(Status.UNKNOWN);
}
public Health(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<String, ?> details) {
Assert.notNull(status, "Status must not be null");
this.status = status; this.status = status;
this.details = new LinkedHashMap<String, Object>(); this.details = Collections.unmodifiableMap(details == null ? NO_DETAILS
: new LinkedHashMap<String, Object>(details));
} }
public Health status(Status status) { /**
Assert.notNull(status, "Status must not be null"); * @return the status of the health (never {@code null})
this.status = status; */
return this; @JsonUnwrapped
public Status getStatus() {
return this.status;
} }
public Health up() { /**
return status(Status.UP); * @return the details of the health or an empty map.
*/
@JsonAnyGetter
public Map<String, Object> getDetails() {
return this.details;
} }
public Health down() { @Override
return status(Status.DOWN); public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj != null && obj instanceof Health) {
Health other = (Health) obj;
return this.status.equals(other.status) && this.details.equals(other.details);
}
return false;
}
@Override
public int 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) { public Health withException(Exception ex) {
Assert.notNull(ex, "Exception must not be null"); Assert.notNull(ex, "Exception must not be null");
return withDetail("error", ex.getClass().getName() + ": " + ex.getMessage()); 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 @JsonAnySetter
public Health withDetail(String key, Object data) { public Health withDetail(String key, Object data) {
Assert.notNull(key, "Key must not be null"); Assert.notNull(key, "Key must not be null");
Assert.notNull(data, "Data must not be null"); Assert.notNull(data, "Data must not be null");
this.details.put(key, data); Map<String, Object> details = new LinkedHashMap<String, Object>(this.details);
return this; details.put(key, data);
return new Health(this.status, details);
} }
@JsonUnwrapped /**
public Status getStatus() { * Create a new {@link Health} instance with an {@link Status#UNKNOWN} status.
return this.status; * @return a new {@link Health} instance
*/
public static Health unknown() {
return status(Status.UNKNOWN);
} }
@JsonAnyGetter /**
public Map<String, Object> getDetails() { * Create a new {@link Health} instance with an {@link Status#UP} status.
return this.details; * @return a new {@link Health} instance
*/
public static Health up() {
return status(Status.UP);
} }
@Override /**
public boolean equals(Object obj) { * Create a new {@link Health} instance with an {@link Status#DOWN} status an the
if (obj == this) { * specified exception details.
return true; * @param ex the exception
* @return a new {@link Health} instance
*/
public static Health down(Exception ex) {
return down().withException(ex);
} }
if (obj != null && obj instanceof Health) {
return ObjectUtils.nullSafeEquals(this.status, ((Health) obj).status) /**
&& ObjectUtils.nullSafeEquals(this.details, ((Health) obj).details); * 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);
} }
return false;
/**
* 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);
} }
@Override /**
public int hashCode() { * Create a new {@link Health} instance with a specific status code.
int hashCode = 0; * @return a new {@link Health} instance
if (this.status != null) { */
hashCode = this.status.hashCode(); public static Health status(String statusCode) {
return status(new Status(statusCode));
} }
return 13 * hashCode + this.details.hashCode();
/**
* 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);
} }
} }
...@@ -38,9 +38,9 @@ public class MongoHealthIndicator extends AbstractHealthIndicator { ...@@ -38,9 +38,9 @@ public class MongoHealthIndicator extends AbstractHealthIndicator {
} }
@Override @Override
protected void doHealthCheck(Health health) throws Exception { protected Health doHealthCheck() throws Exception {
CommandResult result = this.mongoTemplate.executeCommand("{ serverStatus: 1 }"); CommandResult result = this.mongoTemplate.executeCommand("{ serverStatus: 1 }");
health.up().withDetail("version", result.getString("version")); return Health.up().withDetail("version", result.getString("version"));
} }
} }
...@@ -66,14 +66,14 @@ public class OrderedHealthAggregator extends AbstractHealthAggregator { ...@@ -66,14 +66,14 @@ public class OrderedHealthAggregator extends AbstractHealthAggregator {
} }
@Override @Override
protected Status aggregateStatus(List<Status> status) { protected Status aggregateStatus(List<Status> candidates) {
// If no status is given return UNKNOWN // If no status is given return UNKNOWN
if (status.size() == 0) { if (candidates.size() == 0) {
return Status.UNKNOWN; return Status.UNKNOWN;
} }
// Sort given Status instances by configured order // Sort given Status instances by configured order
Collections.sort(status, new StatusComparator(this.statusOrder)); Collections.sort(candidates, new StatusComparator(this.statusOrder));
return status.get(0); return candidates.get(0);
} }
/** /**
......
...@@ -41,17 +41,19 @@ public class RabbitHealthIndicator extends AbstractHealthIndicator { ...@@ -41,17 +41,19 @@ public class RabbitHealthIndicator extends AbstractHealthIndicator {
} }
@Override @Override
protected void doHealthCheck(Health health) throws Exception { protected Health doHealthCheck() throws Exception {
health.up().withDetail("version", return Health.up().withDetail("version", getVersion());
this.rabbitTemplate.execute(new ChannelCallback<String>() { }
private String getVersion() {
return this.rabbitTemplate.execute(new ChannelCallback<String>() {
@Override @Override
public String doInRabbit(Channel channel) throws Exception { public String doInRabbit(Channel channel) throws Exception {
Map<String, Object> serverProperties = channel.getConnection() Map<String, Object> serverProperties = channel.getConnection()
.getServerProperties(); .getServerProperties();
return serverProperties.get("version").toString(); return serverProperties.get("version").toString();
} }
})); });
} }
} }
...@@ -40,12 +40,12 @@ public class RedisHealthIndicator extends AbstractHealthIndicator { ...@@ -40,12 +40,12 @@ public class RedisHealthIndicator extends AbstractHealthIndicator {
} }
@Override @Override
protected void doHealthCheck(Health health) throws Exception { protected Health doHealthCheck() throws Exception {
RedisConnection connection = null; RedisConnection connection = RedisConnectionUtils
.getConnection(this.redisConnectionFactory);
try { try {
connection = RedisConnectionUtils.getConnection(this.redisConnectionFactory);
Properties info = connection.info(); Properties info = connection.info();
health.up().withDetail("version", info.getProperty("redis_version")); return Health.up().withDetail("version", info.getProperty("redis_version"));
} }
finally { finally {
RedisConnectionUtils.releaseConnection(connection, RedisConnectionUtils.releaseConnection(connection,
......
...@@ -35,7 +35,7 @@ import org.springframework.util.StringUtils; ...@@ -35,7 +35,7 @@ import org.springframework.util.StringUtils;
* @author Dave Syer * @author Dave Syer
* @author Christian Dupuis * @author Christian Dupuis
*/ */
public class SimpleDataSourceHealthIndicator implements HealthIndicator { public class SimpleDataSourceHealthIndicator extends AbstractHealthIndicator {
private DataSource dataSource; private DataSource dataSource;
...@@ -63,7 +63,6 @@ public class SimpleDataSourceHealthIndicator implements HealthIndicator { ...@@ -63,7 +63,6 @@ public class SimpleDataSourceHealthIndicator implements HealthIndicator {
/** /**
* Create a new {@link SimpleDataSourceHealthIndicator} using the specified * Create a new {@link SimpleDataSourceHealthIndicator} using the specified
* datasource. * datasource.
*
* @param dataSource the data source * @param dataSource the data source
*/ */
public SimpleDataSourceHealthIndicator(DataSource dataSource) { public SimpleDataSourceHealthIndicator(DataSource dataSource) {
...@@ -72,40 +71,39 @@ public class SimpleDataSourceHealthIndicator implements HealthIndicator { ...@@ -72,40 +71,39 @@ public class SimpleDataSourceHealthIndicator implements HealthIndicator {
} }
@Override @Override
public Health health() { protected Health doHealthCheck() throws Exception {
Health health = new Health(); if (this.dataSource == null) {
health.up(); return Health.up().withDetail("database", "unknown");
String product = "unknown";
if (this.dataSource != null) {
try {
product = this.jdbcTemplate.execute(new ConnectionCallback<String>() {
@Override
public String doInConnection(Connection connection)
throws SQLException, DataAccessException {
return connection.getMetaData().getDatabaseProductName();
}
});
health.withDetail("database", product);
} }
catch (DataAccessException ex) { return doDataSourceHealthCheck();
health.down().withException(ex);
} }
private Health doDataSourceHealthCheck() throws Exception {
String product = getProduct();
Health health = Health.up().withDetail("database", product);
String query = detectQuery(product); String query = detectQuery(product);
if (StringUtils.hasText(query)) { if (StringUtils.hasText(query)) {
try { try {
health.withDetail("hello", health = health.withDetail("hello",
this.jdbcTemplate.queryForObject(query, Object.class)); this.jdbcTemplate.queryForObject(query, Object.class));
} }
catch (Exception ex) { catch (Exception ex) {
health.down().withException(ex); return Health.down().withDetail("database", product).withException(ex);
}
} }
} }
return health; return health;
} }
private String getProduct() {
return this.jdbcTemplate.execute(new ConnectionCallback<String>() {
@Override
public String doInConnection(Connection connection) throws SQLException,
DataAccessException {
return connection.getMetaData().getDatabaseProductName();
}
});
}
protected String detectQuery(String product) { protected String detectQuery(String product) {
String query = this.query; String query = this.query;
if (!StringUtils.hasText(query)) { if (!StringUtils.hasText(query)) {
......
...@@ -33,9 +33,9 @@ public class SolrHealthIndicator extends AbstractHealthIndicator { ...@@ -33,9 +33,9 @@ public class SolrHealthIndicator extends AbstractHealthIndicator {
} }
@Override @Override
protected void doHealthCheck(Health health) throws Exception { protected Health doHealthCheck() throws Exception {
this.solrServer.ping(); Object status = this.solrServer.ping().getResponse().get("status");
health.up().withDetail("solrStatus", return Health.up().withDetail("solrStatus", status);
this.solrServer.ping().getResponse().get("status"));
} }
} }
...@@ -35,7 +35,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; ...@@ -35,7 +35,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
* @since 1.1.0 * @since 1.1.0
*/ */
@JsonInclude(Include.NON_EMPTY) @JsonInclude(Include.NON_EMPTY)
public class Status { public final class Status {
/** /**
* Convenient constant value representing unknown state * Convenient constant value representing unknown state
......
...@@ -25,8 +25,8 @@ package org.springframework.boot.actuate.health; ...@@ -25,8 +25,8 @@ package org.springframework.boot.actuate.health;
public class VanillaHealthIndicator extends AbstractHealthIndicator { public class VanillaHealthIndicator extends AbstractHealthIndicator {
@Override @Override
protected void doHealthCheck(Health health) throws Exception { protected Health doHealthCheck() throws Exception {
health.up(); return Health.up();
} }
} }
...@@ -65,7 +65,7 @@ public class HealthEndpointTests extends AbstractEndpointTests<HealthEndpoint> { ...@@ -65,7 +65,7 @@ public class HealthEndpointTests extends AbstractEndpointTests<HealthEndpoint> {
@Override @Override
public Health health() { public Health health() {
return new Health().status(new Status("FINE")); return Health.status("FINE");
} }
}; };
} }
......
...@@ -51,7 +51,7 @@ public class HealthMvcEndpointTests { ...@@ -51,7 +51,7 @@ public class HealthMvcEndpointTests {
@Test @Test
public void up() { public void up() {
when(this.endpoint.invoke()).thenReturn(new Health().up()); when(this.endpoint.invoke()).thenReturn(Health.up());
Object result = this.mvc.invoke(); Object result = this.mvc.invoke();
assertTrue(result instanceof Health); assertTrue(result instanceof Health);
assertTrue(((Health) result).getStatus() == Status.UP); assertTrue(((Health) result).getStatus() == Status.UP);
...@@ -60,7 +60,7 @@ public class HealthMvcEndpointTests { ...@@ -60,7 +60,7 @@ public class HealthMvcEndpointTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void down() { public void down() {
when(this.endpoint.invoke()).thenReturn(new Health().down()); when(this.endpoint.invoke()).thenReturn(Health.down());
Object result = this.mvc.invoke(); Object result = this.mvc.invoke();
assertTrue(result instanceof ResponseEntity); assertTrue(result instanceof ResponseEntity);
ResponseEntity<Health> response = (ResponseEntity<Health>) result; ResponseEntity<Health> response = (ResponseEntity<Health>) result;
...@@ -71,7 +71,7 @@ public class HealthMvcEndpointTests { ...@@ -71,7 +71,7 @@ public class HealthMvcEndpointTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void customMapping() { 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", this.mvc.setStatusMapping(Collections.singletonMap("OK",
HttpStatus.INTERNAL_SERVER_ERROR)); HttpStatus.INTERNAL_SERVER_ERROR));
Object result = this.mvc.invoke(); Object result = this.mvc.invoke();
......
...@@ -55,9 +55,9 @@ public class CompositeHealthIndicatorTests { ...@@ -55,9 +55,9 @@ public class CompositeHealthIndicatorTests {
@Before @Before
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
given(this.one.health()).willReturn(new Health().withDetail("1", "1")); given(this.one.health()).willReturn(Health.unknown().withDetail("1", "1"));
given(this.two.health()).willReturn(new Health().withDetail("2", "2")); given(this.two.health()).willReturn(Health.unknown().withDetail("2", "2"));
given(this.three.health()).willReturn(new Health().withDetail("3", "3")); given(this.three.health()).willReturn(Health.unknown().withDetail("3", "3"));
this.healthAggregator = new OrderedHealthAggregator(); this.healthAggregator = new OrderedHealthAggregator();
} }
...@@ -72,9 +72,9 @@ public class CompositeHealthIndicatorTests { ...@@ -72,9 +72,9 @@ public class CompositeHealthIndicatorTests {
Health result = composite.health(); Health result = composite.health();
assertThat(result.getDetails().size(), equalTo(2)); assertThat(result.getDetails().size(), equalTo(2));
assertThat(result.getDetails(), assertThat(result.getDetails(),
hasEntry("one", (Object) new Health().withDetail("1", "1"))); hasEntry("one", (Object) Health.unknown().withDetail("1", "1")));
assertThat(result.getDetails(), assertThat(result.getDetails(),
hasEntry("two", (Object) new Health().withDetail("2", "2"))); hasEntry("two", (Object) Health.unknown().withDetail("2", "2")));
} }
@Test @Test
...@@ -88,11 +88,11 @@ public class CompositeHealthIndicatorTests { ...@@ -88,11 +88,11 @@ public class CompositeHealthIndicatorTests {
Health result = composite.health(); Health result = composite.health();
assertThat(result.getDetails().size(), equalTo(3)); assertThat(result.getDetails().size(), equalTo(3));
assertThat(result.getDetails(), assertThat(result.getDetails(),
hasEntry("one", (Object) new Health().withDetail("1", "1"))); hasEntry("one", (Object) Health.unknown().withDetail("1", "1")));
assertThat(result.getDetails(), assertThat(result.getDetails(),
hasEntry("two", (Object) new Health().withDetail("2", "2"))); hasEntry("two", (Object) Health.unknown().withDetail("2", "2")));
assertThat(result.getDetails(), assertThat(result.getDetails(),
hasEntry("three", (Object) new Health().withDetail("3", "3"))); hasEntry("three", (Object) Health.unknown().withDetail("3", "3")));
} }
@Test @Test
...@@ -104,9 +104,9 @@ public class CompositeHealthIndicatorTests { ...@@ -104,9 +104,9 @@ public class CompositeHealthIndicatorTests {
Health result = composite.health(); Health result = composite.health();
assertThat(result.getDetails().size(), equalTo(2)); assertThat(result.getDetails().size(), equalTo(2));
assertThat(result.getDetails(), assertThat(result.getDetails(),
hasEntry("one", (Object) new Health().withDetail("1", "1"))); hasEntry("one", (Object) Health.unknown().withDetail("1", "1")));
assertThat(result.getDetails(), assertThat(result.getDetails(),
hasEntry("two", (Object) new Health().withDetail("2", "2"))); hasEntry("two", (Object) Health.unknown().withDetail("2", "2")));
} }
@Test @Test
......
/*
* 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));
}
}
...@@ -42,10 +42,10 @@ public class OrderedHealthAggregatorTests { ...@@ -42,10 +42,10 @@ public class OrderedHealthAggregatorTests {
@Test @Test
public void defaultOrder() { public void defaultOrder() {
Map<String, Health> healths = new HashMap<String, Health>(); Map<String, Health> healths = new HashMap<String, Health>();
healths.put("h1", new Health(Status.DOWN)); healths.put("h1", Health.status(Status.DOWN));
healths.put("h2", new Health(Status.UP)); healths.put("h2", Health.status(Status.UP));
healths.put("h3", new Health(Status.UNKNOWN)); healths.put("h3", Health.status(Status.UNKNOWN));
healths.put("h4", new Health(Status.OUT_OF_SERVICE)); healths.put("h4", Health.status(Status.OUT_OF_SERVICE));
assertEquals(Status.DOWN, this.healthAggregator.aggregate(healths).getStatus()); assertEquals(Status.DOWN, this.healthAggregator.aggregate(healths).getStatus());
} }
...@@ -54,21 +54,21 @@ public class OrderedHealthAggregatorTests { ...@@ -54,21 +54,21 @@ public class OrderedHealthAggregatorTests {
this.healthAggregator.setStatusOrder(Status.UNKNOWN, Status.UP, this.healthAggregator.setStatusOrder(Status.UNKNOWN, Status.UP,
Status.OUT_OF_SERVICE, Status.DOWN); Status.OUT_OF_SERVICE, Status.DOWN);
Map<String, Health> healths = new HashMap<String, Health>(); Map<String, Health> healths = new HashMap<String, Health>();
healths.put("h1", new Health(Status.DOWN)); healths.put("h1", Health.status(Status.DOWN));
healths.put("h2", new Health(Status.UP)); healths.put("h2", Health.status(Status.UP));
healths.put("h3", new Health(Status.UNKNOWN)); healths.put("h3", Health.status(Status.UNKNOWN));
healths.put("h4", new Health(Status.OUT_OF_SERVICE)); healths.put("h4", Health.status(Status.OUT_OF_SERVICE));
assertEquals(Status.UNKNOWN, this.healthAggregator.aggregate(healths).getStatus()); assertEquals(Status.UNKNOWN, this.healthAggregator.aggregate(healths).getStatus());
} }
@Test @Test
public void defaultOrderWithCustomStatus() { public void defaultOrderWithCustomStatus() {
Map<String, Health> healths = new HashMap<String, Health>(); Map<String, Health> healths = new HashMap<String, Health>();
healths.put("h1", new Health(Status.DOWN)); healths.put("h1", Health.status(Status.DOWN));
healths.put("h2", new Health(Status.UP)); healths.put("h2", Health.status(Status.UP));
healths.put("h3", new Health(Status.UNKNOWN)); healths.put("h3", Health.status(Status.UNKNOWN));
healths.put("h4", new Health(Status.OUT_OF_SERVICE)); healths.put("h4", Health.status(Status.OUT_OF_SERVICE));
healths.put("h5", new Health(new Status("CUSTOM"))); healths.put("h5", Health.status(new Status("CUSTOM")));
assertEquals(new Status("CUSTOM"), this.healthAggregator.aggregate(healths) assertEquals(new Status("CUSTOM"), this.healthAggregator.aggregate(healths)
.getStatus()); .getStatus());
} }
...@@ -78,11 +78,11 @@ public class OrderedHealthAggregatorTests { ...@@ -78,11 +78,11 @@ public class OrderedHealthAggregatorTests {
this.healthAggregator.setStatusOrder(Arrays.asList("DOWN", "OUT_OF_SERVICE", this.healthAggregator.setStatusOrder(Arrays.asList("DOWN", "OUT_OF_SERVICE",
"UP", "UNKNOWN", "CUSTOM")); "UP", "UNKNOWN", "CUSTOM"));
Map<String, Health> healths = new HashMap<String, Health>(); Map<String, Health> healths = new HashMap<String, Health>();
healths.put("h1", new Health(Status.DOWN)); healths.put("h1", Health.status(Status.DOWN));
healths.put("h2", new Health(Status.UP)); healths.put("h2", Health.status(Status.UP));
healths.put("h3", new Health(Status.UNKNOWN)); healths.put("h3", Health.status(Status.UNKNOWN));
healths.put("h4", new Health(Status.OUT_OF_SERVICE)); healths.put("h4", Health.status(Status.OUT_OF_SERVICE));
healths.put("h5", new Health(new Status("CUSTOM"))); healths.put("h5", Health.status(new Status("CUSTOM")));
assertEquals(Status.DOWN, this.healthAggregator.aggregate(healths).getStatus()); assertEquals(Status.DOWN, this.healthAggregator.aggregate(healths).getStatus());
} }
......
...@@ -27,8 +27,10 @@ import org.springframework.jdbc.core.JdbcTemplate; ...@@ -27,8 +27,10 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.SingleConnectionDataSource; import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
...@@ -78,7 +80,7 @@ public class SimpleDataSourceHealthIndicatorTests { ...@@ -78,7 +80,7 @@ public class SimpleDataSourceHealthIndicatorTests {
this.indicator.setDataSource(this.dataSource); this.indicator.setDataSource(this.dataSource);
this.indicator.setQuery("SELECT COUNT(*) from BAR"); this.indicator.setQuery("SELECT COUNT(*) from BAR");
Health health = this.indicator.health(); Health health = this.indicator.health();
assertNotNull(health.getDetails().get("database")); assertThat(health.getDetails().get("database"), notNullValue());
assertEquals(Status.DOWN, health.getStatus()); assertEquals(Status.DOWN, health.getStatus());
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment