Add an actuator WebFlux smoke test

Closes gh-28
This commit is contained in:
Sébastien Deleuze
2023-09-20 10:26:58 +02:00
parent 73ba08ab53
commit 6fa3d0aade
10 changed files with 334 additions and 0 deletions

View File

@@ -0,0 +1 @@
Tests the Spring Boot Actuator on WebFlux.

View File

@@ -0,0 +1,23 @@
plugins {
id "java"
id "org.springframework.boot"
id "org.springframework.cr.smoke-test"
}
dependencies {
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.crac:crac:$cracVersion")
implementation(project(":cr-listener"))
runtimeOnly("io.micrometer:micrometer-registry-prometheus")
testImplementation("org.springframework.boot:spring-boot-starter-test")
appTestImplementation(project(":cr-smoke-test-support"))
}
crSmokeTest {
webApplication = true
}

View File

@@ -0,0 +1,187 @@
package com.example.actuator.webmvc;
import org.junit.jupiter.api.Test;
import org.springframework.cr.smoketest.support.junit.ApplicationTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
@ApplicationTest
class ActuatorWebFluxApplicationAotTests {
@Test
void shouldContainLinks(WebTestClient client) {
client.get()
.uri("/actuator")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$._links.self.templated")
.isEqualTo(false)
.jsonPath("$._links.env-toMatch.templated")
.isEqualTo(true);
}
@Test
void shouldHaveCustomHealthIndicator(WebTestClient client) {
client.get()
.uri("/actuator/health")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$.status")
.isEqualTo("UP")
.jsonPath("$.components.custom.status")
.isEqualTo("UP")
.jsonPath("$.components.custom.details.hello")
.isEqualTo("world");
}
@Test
void shouldHaveAnotherCustomHealthIndicator(WebTestClient client) {
client.get()
.uri("/actuator/health")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$.components.anotherCustom.status")
.isEqualTo("UP");
}
@Test
void shouldHaveCompositeHealth(WebTestClient client) {
client.get()
.uri("/actuator/health")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$.components.composite.status")
.isEqualTo("UP")
.jsonPath("$.components.composite.components.another-custom.status")
.isEqualTo("UP")
.jsonPath("$.components.composite.components.custom.status")
.isEqualTo("UP");
}
@Test
void shouldHaveCustomEndpoint(WebTestClient client) {
client.get()
.uri("/actuator/custom")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.consumeWith((result) -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("custom-read"));
}
@Test
void shouldHaveCustomWebEndpoint(WebTestClient client) {
client.get()
.uri("/actuator/customWeb")
.exchange()
.expectStatus()
.isEqualTo(299)
.expectBody()
.consumeWith(
(result) -> assertThat(new String(result.getResponseBodyContent())).isEqualTo("customWeb-read"));
}
@Test
void shouldHaveReadiness(WebTestClient client) {
client.get()
.uri("/actuator/health/readiness")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$.status")
.isEqualTo("UP");
}
@Test
void shouldHaveEnvInfoProperties(WebTestClient client) {
client.get()
.uri("/actuator/info")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$.app.hello")
.isEqualTo("world");
}
@Test
void shouldHaveJavaInfoProperties(WebTestClient client) {
client.get()
.uri("/actuator/info")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$.java.version")
.isNotEmpty();
}
@Test
void shouldHaveOsInfoProperties(WebTestClient client) {
client.get()
.uri("/actuator/info")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$.os.name")
.isNotEmpty();
}
@Test
void shouldHaveMetrics(WebTestClient client) {
client.get()
.uri("/actuator/metrics/jvm.classes.loaded")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$.measurements.[0].value")
.isNotEmpty();
}
@Test
void prometheusWorks(WebTestClient client) {
client.get()
.uri("/actuator/prometheus")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.consumeWith((result) -> assertThat(new String(result.getResponseBodyContent()))
// Check custom timer
.contains("custom_timer_seconds_max 5.0")
.contains("custom_timer_seconds_count 1.0")
.contains("custom_timer_seconds_sum 5.0")
// Check JVM metric
.contains("# TYPE jvm_threads_peak_threads gauge"));
}
@Test
void shouldHaveLoggers(WebTestClient client) {
client.get()
.uri("/actuator/loggers")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$.levels")
.isNotEmpty()
.jsonPath("$.loggers.['ROOT']")
.isNotEmpty()
.jsonPath("$.loggers.['_org.springframework']")
.isNotEmpty();
}
}

View File

@@ -0,0 +1,28 @@
package com.example.actuator.webmvc;
import java.util.Map;
import com.example.actuator.webmvc.health.AnotherCustomHealthIndicator;
import com.example.actuator.webmvc.health.CustomHealthIndicator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class ActuatorWebfluxApplication {
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(ActuatorWebfluxApplication.class, args);
Thread.currentThread().join(); // To be able to measure memory consumption
}
@Bean("composite")
CompositeHealthContributor compositeHealthContributor(CustomHealthIndicator customHealthIndicator,
AnotherCustomHealthIndicator anotherCustomHealthIndicator) {
return CompositeHealthContributor
.fromMap(Map.of("custom", customHealthIndicator, "another-custom", anotherCustomHealthIndicator));
}
}

View File

@@ -0,0 +1,16 @@
package com.example.actuator.webmvc;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.stereotype.Component;
@Component
@Endpoint(id = "custom")
class CustomEndpoint {
@ReadOperation
String read() {
return "custom-read";
}
}

View File

@@ -0,0 +1,17 @@
package com.example.actuator.webmvc;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
import org.springframework.stereotype.Component;
@Component
@WebEndpoint(id = "customWeb")
class CustomWebEndpoint {
@ReadOperation
WebEndpointResponse<String> read() {
return new WebEndpointResponse<>("customWeb-read", 299);
}
}

View File

@@ -0,0 +1,24 @@
package com.example.actuator.webmvc;
import java.time.Duration;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
class RegisterCustomTimer implements CommandLineRunner {
private final MeterRegistry meterRegistry;
RegisterCustomTimer(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Override
public void run(String... args) {
this.meterRegistry.timer("custom.timer").record(Duration.ofSeconds(5));
}
}

View File

@@ -0,0 +1,15 @@
package com.example.actuator.webmvc.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class AnotherCustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
return Health.up().build();
}
}

View File

@@ -0,0 +1,15 @@
package com.example.actuator.webmvc.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
return Health.up().withDetail("hello", "world").build();
}
}

View File

@@ -0,0 +1,8 @@
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.endpoint.health.show-components=always
management.endpoint.health.probes.enabled=true
management.info.env.enabled=true
management.info.java.enabled=true
management.info.os.enabled=true
info.app.hello=world