Retain defaults when configuring other props on probe health groups

See gh-50296

Signed-off-by: htjworld <itjhan01@gmail.com>
This commit is contained in:
htjworld
2026-09-16 11:26:49 +01:00
committed by Andy Wilkinson
parent 4b51d350ee
commit 5a27fd928e
7 changed files with 179 additions and 48 deletions
@@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProp
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.availability.ApplicationAvailability; import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.health.application.LivenessStateHealthIndicator; import org.springframework.boot.health.application.LivenessStateHealthIndicator;
import org.springframework.boot.health.application.ReadinessStateHealthIndicator; import org.springframework.boot.health.application.ReadinessStateHealthIndicator;
import org.springframework.boot.health.autoconfigure.application.AvailabilityHealthContributorAutoConfiguration; import org.springframework.boot.health.autoconfigure.application.AvailabilityHealthContributorAutoConfiguration;
@@ -40,6 +41,7 @@ import org.springframework.core.env.Environment;
ApplicationAvailabilityAutoConfiguration.class }) ApplicationAvailabilityAutoConfiguration.class })
@ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.annotation.Endpoint") @ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.annotation.Endpoint")
@ConditionalOnBooleanProperty(name = "management.endpoint.health.probes.enabled", matchIfMissing = true) @ConditionalOnBooleanProperty(name = "management.endpoint.health.probes.enabled", matchIfMissing = true)
@EnableConfigurationProperties(HealthEndpointProperties.class)
public final class AvailabilityProbesAutoConfiguration { public final class AvailabilityProbesAutoConfiguration {
@Bean @Bean
@@ -56,8 +58,8 @@ public final class AvailabilityProbesAutoConfiguration {
@Bean @Bean
AvailabilityProbesHealthEndpointGroupsPostProcessor availabilityProbesHealthEndpointGroupsPostProcessor( AvailabilityProbesHealthEndpointGroupsPostProcessor availabilityProbesHealthEndpointGroupsPostProcessor(
Environment environment) { Environment environment, HealthEndpointProperties properties) {
return new AvailabilityProbesHealthEndpointGroupsPostProcessor(environment); return new AvailabilityProbesHealthEndpointGroupsPostProcessor(environment, properties);
} }
} }
@@ -42,22 +42,27 @@ import org.springframework.util.Assert;
* @author Phillip Webb * @author Phillip Webb
* @author Brian Clozel * @author Brian Clozel
* @author Madhura Bhave * @author Madhura Bhave
* @author Taejin Han
*/ */
class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups, AdditionalPathsMapper { class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups, AdditionalPathsMapper {
private final HealthEndpointGroups groups;
private final Map<String, HealthEndpointGroup> probeGroups;
private final Set<String> names;
private static final String LIVENESS = "liveness"; private static final String LIVENESS = "liveness";
private static final String READINESS = "readiness"; private static final String READINESS = "readiness";
AvailabilityProbesHealthEndpointGroups(HealthEndpointGroups groups, boolean addAdditionalPaths) { private final HealthEndpointGroups groups;
private final HealthEndpointProperties properties;
private final Map<String, HealthEndpointGroup> probeGroups;
private final Set<String> names;
AvailabilityProbesHealthEndpointGroups(HealthEndpointGroups groups, boolean addAdditionalPaths,
HealthEndpointProperties properties) {
Assert.notNull(groups, "'groups' must not be null"); Assert.notNull(groups, "'groups' must not be null");
this.groups = groups; this.groups = groups;
this.properties = properties;
this.probeGroups = createProbeGroups(addAdditionalPaths); this.probeGroups = createProbeGroups(addAdditionalPaths);
Set<String> names = new LinkedHashSet<>(groups.getNames()); Set<String> names = new LinkedHashSet<>(groups.getNames());
names.addAll(this.probeGroups.keySet()); names.addAll(this.probeGroups.keySet());
@@ -75,19 +80,34 @@ class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups, Ad
String members) { String members) {
HealthEndpointGroup group = this.groups.get(name); HealthEndpointGroup group = this.groups.get(name);
if (group != null) { if (group != null) {
return determineAdditionalPathForExistingGroup(addAdditionalPath, path, group); if (hasExplicitMembership(name)) {
return determineAdditionalPathForExistingGroup(addAdditionalPath, path, group);
}
return retainProbeDefaultsForExistingGroup(addAdditionalPath, path, group, members);
} }
AdditionalHealthEndpointPath additionalPath = (!addAdditionalPath) ? null AdditionalHealthEndpointPath additionalPath = (!addAdditionalPath) ? null
: AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER, path); : AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER, path);
return new AvailabilityProbesHealthEndpointGroup(additionalPath, members); return new AvailabilityProbesHealthEndpointGroup(additionalPath, members);
} }
private boolean hasExplicitMembership(String name) {
HealthEndpointProperties.Group group = this.properties.getGroup().get(name);
return group != null && (group.getInclude() != null || group.getExclude() != null);
}
private HealthEndpointGroup retainProbeDefaultsForExistingGroup(boolean addAdditionalPath, String path,
HealthEndpointGroup group, String members) {
AdditionalHealthEndpointPath additionalPath = (addAdditionalPath && group.getAdditionalPath() == null)
? AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER, path) : null;
return new DelegatingAvailabilityProbesHealthEndpointGroup(group, additionalPath, Set.of(members));
}
private HealthEndpointGroup determineAdditionalPathForExistingGroup(boolean addAdditionalPath, String path, private HealthEndpointGroup determineAdditionalPathForExistingGroup(boolean addAdditionalPath, String path,
HealthEndpointGroup group) { HealthEndpointGroup group) {
if (addAdditionalPath && group.getAdditionalPath() == null) { if (addAdditionalPath && group.getAdditionalPath() == null) {
AdditionalHealthEndpointPath additionalPath = AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER, AdditionalHealthEndpointPath additionalPath = AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER,
path); path);
return new DelegatingAvailabilityProbesHealthEndpointGroup(group, additionalPath); return new DelegatingAvailabilityProbesHealthEndpointGroup(group, additionalPath, null);
} }
return group; return group;
} }
@@ -28,20 +28,24 @@ import org.springframework.core.env.Environment;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Madhura Bhave * @author Madhura Bhave
* @author Taejin Han
*/ */
@Order(Ordered.LOWEST_PRECEDENCE) @Order(Ordered.LOWEST_PRECEDENCE)
class AvailabilityProbesHealthEndpointGroupsPostProcessor implements HealthEndpointGroupsPostProcessor { class AvailabilityProbesHealthEndpointGroupsPostProcessor implements HealthEndpointGroupsPostProcessor {
private final boolean addAdditionalPaths; private final boolean addAdditionalPaths;
AvailabilityProbesHealthEndpointGroupsPostProcessor(Environment environment) { private final HealthEndpointProperties properties;
AvailabilityProbesHealthEndpointGroupsPostProcessor(Environment environment, HealthEndpointProperties properties) {
this.addAdditionalPaths = "true" this.addAdditionalPaths = "true"
.equalsIgnoreCase(environment.getProperty("management.endpoint.health.probes.add-additional-paths")); .equalsIgnoreCase(environment.getProperty("management.endpoint.health.probes.add-additional-paths"));
this.properties = properties;
} }
@Override @Override
public HealthEndpointGroups postProcessHealthEndpointGroups(HealthEndpointGroups groups) { public HealthEndpointGroups postProcessHealthEndpointGroups(HealthEndpointGroups groups) {
return new AvailabilityProbesHealthEndpointGroups(groups, this.addAdditionalPaths); return new AvailabilityProbesHealthEndpointGroups(groups, this.addAdditionalPaths, this.properties);
} }
} }
@@ -16,6 +16,10 @@
package org.springframework.boot.health.autoconfigure.actuate.endpoint; package org.springframework.boot.health.autoconfigure.actuate.endpoint;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.health.actuate.endpoint.AdditionalHealthEndpointPath; import org.springframework.boot.health.actuate.endpoint.AdditionalHealthEndpointPath;
import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroup; import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroup;
@@ -28,23 +32,27 @@ import org.springframework.util.Assert;
* existing group. * existing group.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Taejin Han
*/ */
class DelegatingAvailabilityProbesHealthEndpointGroup implements HealthEndpointGroup { class DelegatingAvailabilityProbesHealthEndpointGroup implements HealthEndpointGroup {
private final HealthEndpointGroup delegate; private final HealthEndpointGroup delegate;
private final AdditionalHealthEndpointPath additionalPath; private final @Nullable AdditionalHealthEndpointPath additionalPath;
private final @Nullable Set<String> members;
DelegatingAvailabilityProbesHealthEndpointGroup(HealthEndpointGroup delegate, DelegatingAvailabilityProbesHealthEndpointGroup(HealthEndpointGroup delegate,
AdditionalHealthEndpointPath additionalPath) { @Nullable AdditionalHealthEndpointPath additionalPath, @Nullable Set<String> members) {
Assert.notNull(delegate, "'delegate' must not be null"); Assert.notNull(delegate, "'delegate' must not be null");
this.delegate = delegate; this.delegate = delegate;
this.additionalPath = additionalPath; this.additionalPath = additionalPath;
this.members = members;
} }
@Override @Override
public boolean isMember(String name) { public boolean isMember(String name) {
return this.delegate.isMember(name); return (this.members != null) ? this.members.contains(name) : this.delegate.isMember(name);
} }
@Override @Override
@@ -68,8 +76,8 @@ class DelegatingAvailabilityProbesHealthEndpointGroup implements HealthEndpointG
} }
@Override @Override
public AdditionalHealthEndpointPath getAdditionalPath() { public @Nullable AdditionalHealthEndpointPath getAdditionalPath() {
return this.additionalPath; return (this.additionalPath != null) ? this.additionalPath : this.delegate.getAdditionalPath();
} }
} }
@@ -43,7 +43,7 @@ import static org.mockito.Mockito.mock;
class AvailabilityProbesHealthEndpointGroupsPostProcessorTests { class AvailabilityProbesHealthEndpointGroupsPostProcessorTests {
private final AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor( private final AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor(
new MockEnvironment()); new MockEnvironment(), new HealthEndpointProperties());
@Test @Test
void postProcessHealthEndpointGroupsWhenGroupsAlreadyContainedReturnsOriginal() { void postProcessHealthEndpointGroupsWhenGroupsAlreadyContainedReturnsOriginal() {
@@ -102,7 +102,7 @@ class AvailabilityProbesHealthEndpointGroupsPostProcessorTests {
MockEnvironment environment = new MockEnvironment(); MockEnvironment environment = new MockEnvironment();
environment.setProperty("management.endpoint.health.probes.add-additional-paths", "true"); environment.setProperty("management.endpoint.health.probes.add-additional-paths", "true");
AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor( AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor(
environment); environment, new HealthEndpointProperties());
HealthEndpointGroups postProcessed = postProcessor.postProcessHealthEndpointGroups(groups); HealthEndpointGroups postProcessed = postProcessor.postProcessHealthEndpointGroups(groups);
HealthEndpointGroup liveness = postProcessed.get("liveness"); HealthEndpointGroup liveness = postProcessed.get("liveness");
assertThat(liveness).isNotNull(); assertThat(liveness).isNotNull();
@@ -120,7 +120,7 @@ class AvailabilityProbesHealthEndpointGroupsPostProcessorTests {
.willReturn(List.of("/one", "/two", "/three")); .willReturn(List.of("/one", "/two", "/three"));
MockEnvironment environment = new MockEnvironment(); MockEnvironment environment = new MockEnvironment();
AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor( AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor(
environment); environment, new HealthEndpointProperties());
HealthEndpointGroups postProcessed = postProcessor.postProcessHealthEndpointGroups(groups); HealthEndpointGroups postProcessed = postProcessor.postProcessHealthEndpointGroups(groups);
assertThat(postProcessed).isInstanceOf(AdditionalPathsMapper.class); assertThat(postProcessed).isInstanceOf(AdditionalPathsMapper.class);
AdditionalPathsMapper additionalPathsMapper = (AdditionalPathsMapper) postProcessed; AdditionalPathsMapper additionalPathsMapper = (AdditionalPathsMapper) postProcessed;
@@ -137,7 +137,7 @@ class AvailabilityProbesHealthEndpointGroupsPostProcessorTests {
MockEnvironment environment = new MockEnvironment(); MockEnvironment environment = new MockEnvironment();
environment.setProperty("management.endpoint.health.probes.add-additional-paths", "true"); environment.setProperty("management.endpoint.health.probes.add-additional-paths", "true");
AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor( AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor(
environment); environment, new HealthEndpointProperties());
HealthEndpointGroups postProcessed = postProcessor.postProcessHealthEndpointGroups(groups); HealthEndpointGroups postProcessed = postProcessor.postProcessHealthEndpointGroups(groups);
assertThat(postProcessed).isInstanceOf(AdditionalPathsMapper.class); assertThat(postProcessed).isInstanceOf(AdditionalPathsMapper.class);
AdditionalPathsMapper additionalPathsMapper = (AdditionalPathsMapper) postProcessed; AdditionalPathsMapper additionalPathsMapper = (AdditionalPathsMapper) postProcessed;
@@ -149,7 +149,7 @@ class AvailabilityProbesHealthEndpointGroupsPostProcessorTests {
MockEnvironment environment = new MockEnvironment(); MockEnvironment environment = new MockEnvironment();
environment.setProperty("management.endpoint.health.probes.add-additional-paths", value); environment.setProperty("management.endpoint.health.probes.add-additional-paths", value);
AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor( AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor(
environment); environment, new HealthEndpointProperties());
HealthEndpointGroups groups = mock(HealthEndpointGroups.class); HealthEndpointGroups groups = mock(HealthEndpointGroups.class);
return postProcessor.postProcessHealthEndpointGroups(groups); return postProcessor.postProcessHealthEndpointGroups(groups);
} }
@@ -17,6 +17,8 @@
package org.springframework.boot.health.autoconfigure.actuate.endpoint; package org.springframework.boot.health.autoconfigure.actuate.endpoint;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -24,7 +26,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.health.actuate.endpoint.AdditionalHealthEndpointPath; import org.springframework.boot.health.actuate.endpoint.AdditionalHealthEndpointPath;
import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroup; import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroup;
import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroups; import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroups;
import org.springframework.boot.health.actuate.endpoint.HttpCodeStatusMapper;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -36,6 +37,7 @@ import static org.mockito.Mockito.mock;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Madhura Bhave * @author Madhura Bhave
* @author Taejin Han
*/ */
class AvailabilityProbesHealthEndpointGroupsTests { class AvailabilityProbesHealthEndpointGroupsTests {
@@ -52,44 +54,84 @@ class AvailabilityProbesHealthEndpointGroupsTests {
@Test @Test
@SuppressWarnings("NullAway") // Test null check @SuppressWarnings("NullAway") // Test null check
void createWhenGroupsIsNullThrowsException() { void createWhenGroupsIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new AvailabilityProbesHealthEndpointGroups(null, false)) assertThatIllegalArgumentException()
.isThrownBy(() -> new AvailabilityProbesHealthEndpointGroups(null, false, new HealthEndpointProperties()))
.withMessage("'groups' must not be null"); .withMessage("'groups' must not be null");
} }
@Test @Test
void getPrimaryDelegatesToGroups() { void getPrimaryDelegatesToGroups() {
given(this.delegate.getPrimary()).willReturn(this.group); given(this.delegate.getPrimary()).willReturn(this.group);
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false); HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false,
new HealthEndpointProperties());
assertThat(availabilityProbes.getPrimary()).isEqualTo(this.group); assertThat(availabilityProbes.getPrimary()).isEqualTo(this.group);
} }
@Test @Test
void getNamesIncludesAvailabilityProbeGroups() { void getNamesIncludesAvailabilityProbeGroups() {
given(this.delegate.getNames()).willReturn(Collections.singleton("test")); given(this.delegate.getNames()).willReturn(Collections.singleton("test"));
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false); HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false,
new HealthEndpointProperties());
assertThat(availabilityProbes.getNames()).containsExactly("test", "liveness", "readiness"); assertThat(availabilityProbes.getNames()).containsExactly("test", "liveness", "readiness");
} }
@Test @Test
void getWhenProbeInDelegateReturnsOriginalGroup() { void getWhenProbeInDelegateWithExplicitIncludeReturnsOriginalGroup() {
HealthEndpointGroup group = mock(HealthEndpointGroup.class); HealthEndpointGroup group = mock(HealthEndpointGroup.class);
HttpCodeStatusMapper mapper = mock(HttpCodeStatusMapper.class);
given(group.getHttpCodeStatusMapper()).willReturn(mapper);
given(this.delegate.get("liveness")).willReturn(group); given(this.delegate.get("liveness")).willReturn(group);
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false); HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false,
propertiesWithInclude("liveness", "livenessState", "diskSpace"));
assertThat(availabilityProbes.get("liveness")).isEqualTo(group); assertThat(availabilityProbes.get("liveness")).isEqualTo(group);
assertThat(group.getHttpCodeStatusMapper()).isEqualTo(mapper);
} }
@Test @Test
void getWhenProbeInDelegateAndExistingAdditionalPathReturnsOriginalGroup() { void getWhenProbeInDelegateWithNoExplicitMembershipRetainsProbeDefaults() {
given(this.delegate.get("liveness")).willReturn(this.group);
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false,
new HealthEndpointProperties());
HealthEndpointGroup liveness = availabilityProbes.get("liveness");
assertThat(liveness).isNotNull();
assertThat(liveness.isMember("livenessState")).isTrue();
assertThat(liveness.isMember("diskSpace")).isFalse();
}
@Test
void getWhenProbeInDelegateWithPropertiesGroupButNoMembershipRetainsProbeDefaults() {
given(this.delegate.get("liveness")).willReturn(this.group);
HealthEndpointProperties.Group propertiesGroup = new HealthEndpointProperties.Group();
propertiesGroup.setAdditionalPath("server:/custom");
HealthEndpointProperties properties = new HealthEndpointProperties();
properties.getGroup().put("liveness", propertiesGroup);
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false,
properties);
HealthEndpointGroup liveness = availabilityProbes.get("liveness");
assertThat(liveness).isNotNull();
assertThat(liveness.isMember("livenessState")).isTrue();
assertThat(liveness.isMember("diskSpace")).isFalse();
}
@Test
void getWhenReadinessProbeInDelegateWithNoExplicitMembershipRetainsProbeDefaults() {
given(this.delegate.get("readiness")).willReturn(this.group);
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false,
new HealthEndpointProperties());
HealthEndpointGroup readiness = availabilityProbes.get("readiness");
assertThat(readiness).isNotNull();
assertThat(readiness.isMember("readinessState")).isTrue();
assertThat(readiness.isMember("diskSpace")).isFalse();
}
@Test
void getWhenProbeInDelegateAndExistingAdditionalPathPreservesPathWithProbeDefaults() {
HealthEndpointGroup group = mock(HealthEndpointGroup.class); HealthEndpointGroup group = mock(HealthEndpointGroup.class);
given(group.getAdditionalPath()).willReturn(AdditionalHealthEndpointPath.from("server:test")); given(group.getAdditionalPath()).willReturn(AdditionalHealthEndpointPath.from("server:test"));
given(this.delegate.get("liveness")).willReturn(group); given(this.delegate.get("liveness")).willReturn(group);
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, true); HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, true,
new HealthEndpointProperties());
HealthEndpointGroup liveness = availabilityProbes.get("liveness"); HealthEndpointGroup liveness = availabilityProbes.get("liveness");
assertThat(liveness).isNotNull(); assertThat(liveness).isNotNull();
assertThat(liveness).isEqualTo(group); assertThat(liveness.isMember("livenessState")).isTrue();
assertThat(liveness.isMember("diskSpace")).isFalse();
AdditionalHealthEndpointPath additionalPath = liveness.getAdditionalPath(); AdditionalHealthEndpointPath additionalPath = liveness.getAdditionalPath();
assertThat(additionalPath).isNotNull(); assertThat(additionalPath).isNotNull();
assertThat(additionalPath.getValue()).isEqualTo("test"); assertThat(additionalPath.getValue()).isEqualTo("test");
@@ -98,9 +140,12 @@ class AvailabilityProbesHealthEndpointGroupsTests {
@Test @Test
void getWhenProbeInDelegateAndAdditionalPathReturnsGroupWithAdditionalPath() { void getWhenProbeInDelegateAndAdditionalPathReturnsGroupWithAdditionalPath() {
given(this.delegate.get("liveness")).willReturn(this.group); given(this.delegate.get("liveness")).willReturn(this.group);
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, true); HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, true,
new HealthEndpointProperties());
HealthEndpointGroup liveness = availabilityProbes.get("liveness"); HealthEndpointGroup liveness = availabilityProbes.get("liveness");
assertThat(liveness).isNotNull(); assertThat(liveness).isNotNull();
assertThat(liveness.isMember("livenessState")).isTrue();
assertThat(liveness.isMember("diskSpace")).isFalse();
AdditionalHealthEndpointPath additionalPath = liveness.getAdditionalPath(); AdditionalHealthEndpointPath additionalPath = liveness.getAdditionalPath();
assertThat(additionalPath).isNotNull(); assertThat(additionalPath).isNotNull();
assertThat(additionalPath.getValue()).isEqualTo("/livez"); assertThat(additionalPath.getValue()).isEqualTo("/livez");
@@ -108,19 +153,22 @@ class AvailabilityProbesHealthEndpointGroupsTests {
@Test @Test
void getWhenProbeNotInDelegateReturnsProbeGroup() { void getWhenProbeNotInDelegateReturnsProbeGroup() {
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false); HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false,
new HealthEndpointProperties());
assertThat(availabilityProbes.get("liveness")).isInstanceOf(AvailabilityProbesHealthEndpointGroup.class); assertThat(availabilityProbes.get("liveness")).isInstanceOf(AvailabilityProbesHealthEndpointGroup.class);
} }
@Test @Test
void getWhenNotProbeAndNotInDelegateReturnsNull() { void getWhenNotProbeAndNotInDelegateReturnsNull() {
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false); HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false,
new HealthEndpointProperties());
assertThat(availabilityProbes.get("mygroup")).isNull(); assertThat(availabilityProbes.get("mygroup")).isNull();
} }
@Test @Test
void getLivenessProbeHasOnlyLivenessStateAsMember() { void getLivenessProbeHasOnlyLivenessStateAsMember() {
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false); HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false,
new HealthEndpointProperties());
HealthEndpointGroup probeGroup = availabilityProbes.get("liveness"); HealthEndpointGroup probeGroup = availabilityProbes.get("liveness");
assertThat(probeGroup).isNotNull(); assertThat(probeGroup).isNotNull();
assertThat(probeGroup.isMember("livenessState")).isTrue(); assertThat(probeGroup.isMember("livenessState")).isTrue();
@@ -129,11 +177,37 @@ class AvailabilityProbesHealthEndpointGroupsTests {
@Test @Test
void getReadinessProbeHasOnlyReadinessStateAsMember() { void getReadinessProbeHasOnlyReadinessStateAsMember() {
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false); HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false,
new HealthEndpointProperties());
HealthEndpointGroup probeGroup = availabilityProbes.get("readiness"); HealthEndpointGroup probeGroup = availabilityProbes.get("readiness");
assertThat(probeGroup).isNotNull(); assertThat(probeGroup).isNotNull();
assertThat(probeGroup.isMember("livenessState")).isFalse(); assertThat(probeGroup.isMember("livenessState")).isFalse();
assertThat(probeGroup.isMember("readinessState")).isTrue(); assertThat(probeGroup.isMember("readinessState")).isTrue();
} }
@Test
void getWhenProbeInDelegateWithExplicitExcludeReturnsOriginalGroup() {
HealthEndpointGroup group = mock(HealthEndpointGroup.class);
given(this.delegate.get("liveness")).willReturn(group);
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false,
propertiesWithExclude("liveness", "diskSpace"));
assertThat(availabilityProbes.get("liveness")).isEqualTo(group);
}
private HealthEndpointProperties propertiesWithInclude(String probe, String... include) {
HealthEndpointProperties properties = new HealthEndpointProperties();
HealthEndpointProperties.Group group = new HealthEndpointProperties.Group();
group.setInclude(new LinkedHashSet<>(Set.of(include)));
properties.getGroup().put(probe, group);
return properties;
}
private HealthEndpointProperties propertiesWithExclude(String probe, String... exclude) {
HealthEndpointProperties properties = new HealthEndpointProperties();
HealthEndpointProperties.Group group = new HealthEndpointProperties.Group();
group.setExclude(new LinkedHashSet<>(Set.of(exclude)));
properties.getGroup().put(probe, group);
return properties;
}
} }
@@ -16,6 +16,8 @@
package org.springframework.boot.health.autoconfigure.actuate.endpoint; package org.springframework.boot.health.autoconfigure.actuate.endpoint;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -34,9 +36,12 @@ import static org.mockito.Mockito.mock;
* Tests for {@link DelegatingAvailabilityProbesHealthEndpointGroup}. * Tests for {@link DelegatingAvailabilityProbesHealthEndpointGroup}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Taejin Han
*/ */
class DelegatingAvailabilityProbesHealthEndpointGroupTests { class DelegatingAvailabilityProbesHealthEndpointGroupTests {
private HealthEndpointGroup delegate;
private DelegatingAvailabilityProbesHealthEndpointGroup group; private DelegatingAvailabilityProbesHealthEndpointGroup group;
private HttpCodeStatusMapper mapper; private HttpCodeStatusMapper mapper;
@@ -45,16 +50,16 @@ class DelegatingAvailabilityProbesHealthEndpointGroupTests {
@BeforeEach @BeforeEach
void setup() { void setup() {
HealthEndpointGroup delegate = mock(HealthEndpointGroup.class); this.delegate = mock(HealthEndpointGroup.class);
this.mapper = mock(HttpCodeStatusMapper.class); this.mapper = mock(HttpCodeStatusMapper.class);
this.aggregator = mock(StatusAggregator.class); this.aggregator = mock(StatusAggregator.class);
given(delegate.getHttpCodeStatusMapper()).willReturn(this.mapper); given(this.delegate.getHttpCodeStatusMapper()).willReturn(this.mapper);
given(delegate.getStatusAggregator()).willReturn(this.aggregator); given(this.delegate.getStatusAggregator()).willReturn(this.aggregator);
given(delegate.showComponents(any())).willReturn(true); given(this.delegate.showComponents(any())).willReturn(true);
given(delegate.showDetails(any())).willReturn(false); given(this.delegate.showDetails(any())).willReturn(false);
given(delegate.isMember("test")).willReturn(true); given(this.delegate.isMember("test")).willReturn(true);
this.group = new DelegatingAvailabilityProbesHealthEndpointGroup(delegate, this.group = new DelegatingAvailabilityProbesHealthEndpointGroup(this.delegate,
AdditionalHealthEndpointPath.from("server:test")); AdditionalHealthEndpointPath.from("server:test"), null);
} }
@Test @Test
@@ -64,7 +69,25 @@ class DelegatingAvailabilityProbesHealthEndpointGroupTests {
assertThat(this.group.isMember("test")).isTrue(); assertThat(this.group.isMember("test")).isTrue();
assertThat(this.group.showDetails(SecurityContext.NONE)).isFalse(); assertThat(this.group.showDetails(SecurityContext.NONE)).isFalse();
assertThat(this.group.showComponents(SecurityContext.NONE)).isTrue(); assertThat(this.group.showComponents(SecurityContext.NONE)).isTrue();
assertThat(this.group.getAdditionalPath()).isNotNull();
assertThat(this.group.getAdditionalPath().getValue()).isEqualTo("test"); assertThat(this.group.getAdditionalPath().getValue()).isEqualTo("test");
} }
@Test
void groupWithMembersOverrideDelegateMembership() {
DelegatingAvailabilityProbesHealthEndpointGroup group = new DelegatingAvailabilityProbesHealthEndpointGroup(
this.delegate, AdditionalHealthEndpointPath.from("server:test"), Set.of("livenessState"));
assertThat(group.isMember("livenessState")).isTrue();
assertThat(group.isMember("test")).isFalse();
}
@Test
void groupWithNullAdditionalPathDelegatesToDelegateAdditionalPath() {
given(this.delegate.getAdditionalPath()).willReturn(AdditionalHealthEndpointPath.from("server:delegated"));
DelegatingAvailabilityProbesHealthEndpointGroup group = new DelegatingAvailabilityProbesHealthEndpointGroup(
this.delegate, null, null);
assertThat(group.getAdditionalPath()).isNotNull();
assertThat(group.getAdditionalPath().getValue()).isEqualTo("delegated");
}
} }