package com.ankurm.actuator; import com.ankurm.actuator.health.UpstreamState; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.health.contributor.Status; import org.springframework.boot.health.registry.HealthContributorRegistry; import org.springframework.boot.health.contributor.HealthIndicator; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import static org.assertj.core.api.Assertions.assertThat; /** * The contract that matters operationally: an upstream outage must move readiness and must NOT * move liveness. * *

See docs/07-groups-and-probes.md. */ // DEFINED_PORT, not the default MOCK environment. ExternalApiHealthIndicator makes a real // HTTP call to this application's own stub controller, so a servlet container has to be // listening on the port the indicator was configured with. With the mock environment the // indicator reports DOWN with "Connection refused" and the test proves nothing. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @ActiveProfiles("groups") class HealthGroupTests { @Autowired private HealthContributorRegistry registry; @Autowired private UpstreamState upstream; @AfterEach void reset() { this.upstream.set(UpstreamState.Mode.UP); } private Status statusOf(String name) { var contributor = this.registry.getContributor(name); assertThat(contributor).as("contributor '%s' is registered", name).isNotNull(); return ((HealthIndicator) contributor).health().getStatus(); } @Test void everyCustomIndicatorIsRegisteredUnderTheExpectedName() { // The bean name, minus a trailing "HealthIndicator", is the key in the JSON response. // Rename the bean and you silently break every dashboard that reads it. assertThat(statusOf("ordersDatabase")).isNotNull(); assertThat(statusOf("externalApi")).isNotNull(); assertThat(statusOf("kafka")).isNotNull(); } @Test void upstreamOutageMovesTheExternalApiIndicatorButNotLiveness() { assertThat(statusOf("externalApi")).isEqualTo(Status.UP); assertThat(statusOf("livenessState")).isEqualTo(Status.UP); this.upstream.set(UpstreamState.Mode.DOWN); assertThat(statusOf("externalApi")).isEqualTo(Status.DOWN); assertThat(statusOf("livenessState")) .as("a third-party outage must never make this process look unrecoverable") .isEqualTo(Status.UP); } @Test void theExternalApiIndicatorRespectsItsTimeoutBudget() { this.upstream.set(UpstreamState.Mode.SLOW); long start = System.nanoTime(); Status status = statusOf("externalApi"); long elapsedMs = (System.nanoTime() - start) / 1_000_000; assertThat(status).isEqualTo(Status.DOWN); // The stub sleeps 30s. demo.upstream.timeout-ms is 750. If this assertion ever fails, // someone removed the read timeout and the health endpoint can now block a worker. assertThat(elapsedMs).isLessThan(5_000L); } }