Commit ad4ce9cf authored by Andy Wilkinson's avatar Andy Wilkinson

Return objects from trace, audit event, and thread dump endpoints

Closes gh-7648
parent a6b30a3a
...@@ -18,15 +18,11 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web; ...@@ -18,15 +18,11 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import org.springframework.boot.actuate.endpoint.web.AuditEventsWebEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint; import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.LogFileWebEndpoint; import org.springframework.boot.actuate.endpoint.web.LogFileWebEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConditionContext;
...@@ -53,15 +49,6 @@ public class WebEndpointManagementContextConfiguration { ...@@ -53,15 +49,6 @@ public class WebEndpointManagementContextConfiguration {
return new HeapDumpWebEndpoint(); return new HeapDumpWebEndpoint();
} }
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(value = AuditEventsEndpoint.class, search = SearchStrategy.CURRENT)
public AuditEventsWebEndpointExtension auditEventsWebEndpointExtension(
AuditEventsEndpoint delegate) {
return new AuditEventsWebEndpointExtension(delegate);
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@Conditional(LogFileCondition.class) @Conditional(LogFileCondition.class)
......
...@@ -42,9 +42,28 @@ public class AuditEventsEndpoint { ...@@ -42,9 +42,28 @@ public class AuditEventsEndpoint {
} }
@ReadOperation @ReadOperation
public List<AuditEvent> eventsWithPrincipalDateAfterAndType(String principal, public AuditEventsDescriptor eventsWithPrincipalDateAfterAndType(String principal,
Date after, String type) { Date after, String type) {
return this.auditEventRepository.find(principal, after, type); return new AuditEventsDescriptor(
this.auditEventRepository.find(principal, after, type));
}
/**
* A description of an application's {@link AuditEvent audit events}. Primarily
* intended for serialization to JSON.
*/
public static final class AuditEventsDescriptor {
private final List<AuditEvent> events;
private AuditEventsDescriptor(List<AuditEvent> events) {
this.events = events;
}
public List<AuditEvent> getEvents() {
return this.events;
}
} }
} }
...@@ -28,14 +28,32 @@ import org.springframework.boot.endpoint.ReadOperation; ...@@ -28,14 +28,32 @@ import org.springframework.boot.endpoint.ReadOperation;
* {@link Endpoint} to expose thread info. * {@link Endpoint} to expose thread info.
* *
* @author Dave Syer * @author Dave Syer
* @author Andy Wilkinson
*/ */
@Endpoint(id = "threaddump") @Endpoint(id = "threaddump")
public class ThreadDumpEndpoint { public class ThreadDumpEndpoint {
@ReadOperation @ReadOperation
public List<ThreadInfo> threadDump() { public ThreadDumpDescriptor threadDump() {
return Arrays return new ThreadDumpDescriptor(Arrays
.asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)); .asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)));
}
/**
* A description of a thread dump. Primarily intended for serialization to JSON.
*/
public static final class ThreadDumpDescriptor {
private final List<ThreadInfo> threads;
private ThreadDumpDescriptor(List<ThreadInfo> threads) {
this.threads = threads;
}
public List<ThreadInfo> getThreads() {
return this.threads;
}
} }
} }
...@@ -44,8 +44,26 @@ public class TraceEndpoint { ...@@ -44,8 +44,26 @@ public class TraceEndpoint {
} }
@ReadOperation @ReadOperation
public List<Trace> traces() { public TraceDescriptor traces() {
return this.repository.findAll(); return new TraceDescriptor(this.repository.findAll());
}
/**
* A description of an application's {@link Trace} entries. Primarily intended for
* serialization to JSON.
*/
public static final class TraceDescriptor {
private final List<Trace> traces;
private TraceDescriptor(List<Trace> traces) {
this.traces = traces;
}
public List<Trace> getTraces() {
return this.traces;
}
} }
} }
...@@ -17,10 +17,9 @@ ...@@ -17,10 +17,9 @@
package org.springframework.boot.actuate.endpoint.jmx; package org.springframework.boot.actuate.endpoint.jmx;
import java.util.Date; import java.util.Date;
import java.util.List;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint.AuditEventsDescriptor;
import org.springframework.boot.endpoint.ReadOperation; import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.boot.endpoint.jmx.JmxEndpointExtension; import org.springframework.boot.endpoint.jmx.JmxEndpointExtension;
...@@ -41,12 +40,12 @@ public class AuditEventsJmxEndpointExtension { ...@@ -41,12 +40,12 @@ public class AuditEventsJmxEndpointExtension {
} }
@ReadOperation @ReadOperation
public List<AuditEvent> eventsWithDateAfter(Date dateAfter) { public AuditEventsDescriptor eventsWithDateAfter(Date dateAfter) {
return this.delegate.eventsWithPrincipalDateAfterAndType(null, dateAfter, null); return this.delegate.eventsWithPrincipalDateAfterAndType(null, dateAfter, null);
} }
@ReadOperation @ReadOperation
public List<AuditEvent> eventsWithPrincipalAndDateAfter(String principal, public AuditEventsDescriptor eventsWithPrincipalAndDateAfter(String principal,
Date dateAfter) { Date dateAfter) {
return this.delegate.eventsWithPrincipalDateAfterAndType(principal, dateAfter, return this.delegate.eventsWithPrincipalDateAfterAndType(principal, dateAfter,
null); null);
......
/*
* Copyright 2012-2017 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.endpoint.web;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import org.springframework.boot.endpoint.ReadOperation;
import org.springframework.boot.endpoint.web.WebEndpointExtension;
/**
* Web-specific extension of the {@link AuditEventsEndpoint}.
*
* @author Andy Wilkinson
* @since 2.0.0
*/
@WebEndpointExtension(endpoint = AuditEventsEndpoint.class)
public class AuditEventsWebEndpointExtension {
private final AuditEventsEndpoint delegate;
public AuditEventsWebEndpointExtension(AuditEventsEndpoint delegate) {
this.delegate = delegate;
}
@ReadOperation
public Map<String, List<AuditEvent>> eventsWithPrincipalDateAfterAndType(
String principal, Date after, String type) {
return Collections.singletonMap("events", this.delegate
.eventsWithPrincipalDateAfterAndType(principal, after, type));
}
}
...@@ -24,7 +24,6 @@ import org.springframework.boot.actuate.audit.AuditEventRepository; ...@@ -24,7 +24,6 @@ import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint; import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.StatusEndpoint; import org.springframework.boot.actuate.endpoint.StatusEndpoint;
import org.springframework.boot.actuate.endpoint.web.AuditEventsWebEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.HealthReactiveWebEndpointExtension; import org.springframework.boot.actuate.endpoint.web.HealthReactiveWebEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension; import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint; import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint;
...@@ -143,9 +142,10 @@ public class WebEndpointManagementContextConfigurationTests { ...@@ -143,9 +142,10 @@ public class WebEndpointManagementContextConfigurationTests {
public void reactiveHealthWebEndpointExtensionCanBeDisabled() { public void reactiveHealthWebEndpointExtensionCanBeDisabled() {
reactiveWebContextRunner(HealthEndpointConfiguration.class) reactiveWebContextRunner(HealthEndpointConfiguration.class)
.withPropertyValues("endpoints.health.enabled=false").run((context) -> { .withPropertyValues("endpoints.health.enabled=false").run((context) -> {
assertThat(context).doesNotHaveBean(HealthReactiveWebEndpointExtension.class); assertThat(context)
assertThat(context).doesNotHaveBean(HealthWebEndpointExtension.class); .doesNotHaveBean(HealthReactiveWebEndpointExtension.class);
}); assertThat(context).doesNotHaveBean(HealthWebEndpointExtension.class);
});
} }
...@@ -175,22 +175,10 @@ public class WebEndpointManagementContextConfigurationTests { ...@@ -175,22 +175,10 @@ public class WebEndpointManagementContextConfigurationTests {
public void reactiveStatusWebEndpointExtensionCanBeDisabled() { public void reactiveStatusWebEndpointExtensionCanBeDisabled() {
reactiveWebContextRunner(StatusEndpointConfiguration.class) reactiveWebContextRunner(StatusEndpointConfiguration.class)
.withPropertyValues("endpoints.status.enabled=false").run((context) -> { .withPropertyValues("endpoints.status.enabled=false").run((context) -> {
assertThat(context).doesNotHaveBean(StatusReactiveWebEndpointExtension.class); assertThat(context)
assertThat(context).doesNotHaveBean(StatusWebEndpointExtension.class); .doesNotHaveBean(StatusReactiveWebEndpointExtension.class);
}); assertThat(context).doesNotHaveBean(StatusWebEndpointExtension.class);
} });
@Test
public void auditEventsWebEndpointExtensionIsAutoConfigured() {
beanIsAutoConfigured(AuditEventsWebEndpointExtension.class,
AuditEventsEndpointConfiguration.class);
}
@Test
public void auditEventsWebEndpointExtensionCanBeDisabled() {
beanIsNotAutoConfiguredWhenEndpointIsDisabled(
AuditEventsWebEndpointExtension.class, "auditevents",
AuditEventsEndpointConfiguration.class);
} }
@Test @Test
...@@ -209,7 +197,8 @@ public class WebEndpointManagementContextConfigurationTests { ...@@ -209,7 +197,8 @@ public class WebEndpointManagementContextConfigurationTests {
@Test @Test
public void logFileWebEndpointIsAutoConfiguredWhenExternalFileIsSet() { public void logFileWebEndpointIsAutoConfiguredWhenExternalFileIsSet() {
webContextRunner().withPropertyValues("endpoints.logfile.external-file:external.log") webContextRunner()
.withPropertyValues("endpoints.logfile.external-file:external.log")
.run((context) -> assertThat( .run((context) -> assertThat(
context.getBeansOfType(LogFileWebEndpoint.class)).hasSize(1)); context.getBeansOfType(LogFileWebEndpoint.class)).hasSize(1));
} }
......
...@@ -47,8 +47,8 @@ public class AuditEventsEndpointTests { ...@@ -47,8 +47,8 @@ public class AuditEventsEndpointTests {
public void eventsWithType() { public void eventsWithType() {
given(this.repository.find(null, null, "type")) given(this.repository.find(null, null, "type"))
.willReturn(Collections.singletonList(this.event)); .willReturn(Collections.singletonList(this.event));
List<AuditEvent> result = this.endpoint.eventsWithPrincipalDateAfterAndType(null, List<AuditEvent> result = this.endpoint
null, "type"); .eventsWithPrincipalDateAfterAndType(null, null, "type").getEvents();
assertThat(result).isEqualTo(Collections.singletonList(this.event)); assertThat(result).isEqualTo(Collections.singletonList(this.event));
} }
...@@ -57,8 +57,8 @@ public class AuditEventsEndpointTests { ...@@ -57,8 +57,8 @@ public class AuditEventsEndpointTests {
Date date = new Date(); Date date = new Date();
given(this.repository.find(null, date, null)) given(this.repository.find(null, date, null))
.willReturn(Collections.singletonList(this.event)); .willReturn(Collections.singletonList(this.event));
List<AuditEvent> result = this.endpoint.eventsWithPrincipalDateAfterAndType(null, List<AuditEvent> result = this.endpoint
date, null); .eventsWithPrincipalDateAfterAndType(null, date, null).getEvents();
assertThat(result).isEqualTo(Collections.singletonList(this.event)); assertThat(result).isEqualTo(Collections.singletonList(this.event));
} }
...@@ -67,7 +67,7 @@ public class AuditEventsEndpointTests { ...@@ -67,7 +67,7 @@ public class AuditEventsEndpointTests {
given(this.repository.find("Joan", null, null)) given(this.repository.find("Joan", null, null))
.willReturn(Collections.singletonList(this.event)); .willReturn(Collections.singletonList(this.event));
List<AuditEvent> result = this.endpoint List<AuditEvent> result = this.endpoint
.eventsWithPrincipalDateAfterAndType("Joan", null, null); .eventsWithPrincipalDateAfterAndType("Joan", null, null).getEvents();
assertThat(result).isEqualTo(Collections.singletonList(this.event)); assertThat(result).isEqualTo(Collections.singletonList(this.event));
} }
......
...@@ -30,7 +30,8 @@ public class ThreadDumpEndpointTests { ...@@ -30,7 +30,8 @@ public class ThreadDumpEndpointTests {
@Test @Test
public void dumpThreads() throws Exception { public void dumpThreads() throws Exception {
assertThat(new ThreadDumpEndpoint().threadDump().size()).isGreaterThan(0); assertThat(new ThreadDumpEndpoint().threadDump().getThreads().size())
.isGreaterThan(0);
} }
} }
...@@ -38,7 +38,7 @@ public class TraceEndpointTests { ...@@ -38,7 +38,7 @@ public class TraceEndpointTests {
public void trace() throws Exception { public void trace() throws Exception {
TraceRepository repository = new InMemoryTraceRepository(); TraceRepository repository = new InMemoryTraceRepository();
repository.add(Collections.<String, Object>singletonMap("a", "b")); repository.add(Collections.<String, Object>singletonMap("a", "b"));
Trace trace = new TraceEndpoint(repository).traces().get(0); Trace trace = new TraceEndpoint(repository).traces().getTraces().get(0);
assertThat(trace.getInfo().get("a")).isEqualTo("b"); assertThat(trace.getInfo().get("a")).isEqualTo("b");
} }
......
...@@ -50,7 +50,7 @@ public class AuditEventsJmxEndpointExtensionTests { ...@@ -50,7 +50,7 @@ public class AuditEventsJmxEndpointExtensionTests {
Date date = new Date(); Date date = new Date();
given(this.repository.find(null, date, null)) given(this.repository.find(null, date, null))
.willReturn(Collections.singletonList(this.event)); .willReturn(Collections.singletonList(this.event));
List<AuditEvent> result = this.extension.eventsWithDateAfter(date); List<AuditEvent> result = this.extension.eventsWithDateAfter(date).getEvents();
assertThat(result).isEqualTo(Collections.singletonList(this.event)); assertThat(result).isEqualTo(Collections.singletonList(this.event));
} }
...@@ -59,8 +59,8 @@ public class AuditEventsJmxEndpointExtensionTests { ...@@ -59,8 +59,8 @@ public class AuditEventsJmxEndpointExtensionTests {
Date date = new Date(); Date date = new Date();
given(this.repository.find("Joan", date, null)) given(this.repository.find("Joan", date, null))
.willReturn(Collections.singletonList(this.event)); .willReturn(Collections.singletonList(this.event));
List<AuditEvent> result = this.extension.eventsWithPrincipalAndDateAfter("Joan", List<AuditEvent> result = this.extension
date); .eventsWithPrincipalAndDateAfter("Joan", date).getEvents();
assertThat(result).isEqualTo(Collections.singletonList(this.event)); assertThat(result).isEqualTo(Collections.singletonList(this.event));
} }
......
...@@ -33,8 +33,8 @@ import org.springframework.context.annotation.Configuration; ...@@ -33,8 +33,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
/** /**
* Integration tests for {@link AuditEventsEndpoint} and * Integration tests for {@link AuditEventsEndpoint} exposed by Jersey, Spring MVC, and
* {@link AuditEventsWebEndpointExtension} exposed by Jersey, Spring MVC, and WebFlux. * WebFlux.
* *
* @author Vedran Pavic * @author Vedran Pavic
* @author Andy Wilkinson * @author Andy Wilkinson
...@@ -95,11 +95,6 @@ public class AuditEventsEndpointWebIntegrationTests { ...@@ -95,11 +95,6 @@ public class AuditEventsEndpointWebIntegrationTests {
return new AuditEventsEndpoint(auditEventsRepository()); return new AuditEventsEndpoint(auditEventsRepository());
} }
@Bean
public AuditEventsWebEndpointExtension auditEventsWebEndpointExtension() {
return new AuditEventsWebEndpointExtension(auditEventsEndpoint());
}
private AuditEvent createEvent(String instant, String principal, String type) { private AuditEvent createEvent(String instant, String principal, String type) {
return new AuditEvent(Date.from(Instant.parse(instant)), principal, type, return new AuditEvent(Date.from(Instant.parse(instant)), principal, type,
Collections.<String, Object>emptyMap()); Collections.<String, Object>emptyMap());
......
/*
* Copyright 2012-2017 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.endpoint.web;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AuditEventsWebEndpointExtension}.
*
* @author Andy Wilkinson
*/
public class AuditEventsWebEndpointExtensionTests {
private final AuditEventRepository repository = mock(AuditEventRepository.class);
private final AuditEventsWebEndpointExtension extension = new AuditEventsWebEndpointExtension(
new AuditEventsEndpoint(this.repository));
private final AuditEvent event = new AuditEvent("principal", "type",
Collections.singletonMap("a", "alpha"));
@Test
public void delegatesResponseIsAvailableFromEventsKeyInMap() {
Date date = new Date();
given(this.repository.find("principal", date, "type"))
.willReturn(Collections.singletonList(this.event));
Map<String, List<AuditEvent>> result = this.extension
.eventsWithPrincipalDateAfterAndType("principal", date, "type");
assertThat(result).hasSize(1);
assertThat(result).containsEntry("events", Collections.singletonList(this.event));
}
}
...@@ -163,35 +163,35 @@ public class SampleActuatorApplicationTests { ...@@ -163,35 +163,35 @@ public class SampleActuatorApplicationTests {
} }
@Test @Test
@SuppressWarnings("unchecked")
public void testTrace() throws Exception { public void testTrace() throws Exception {
this.restTemplate.getForEntity("/health", String.class); this.restTemplate.getForEntity("/health", String.class);
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
ResponseEntity<List> entity = this.restTemplate ResponseEntity<Map> entity = this.restTemplate
.withBasicAuth("user", getPassword()) .withBasicAuth("user", getPassword())
.getForEntity("/application/trace", List.class); .getForEntity("/application/trace", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@SuppressWarnings("unchecked") Map<String, Object> body = entity.getBody();
List<Map<String, Object>> list = entity.getBody(); Map<String, Object> trace = ((List<Map<String, Object>>) body.get("traces"))
Map<String, Object> trace = list.get(list.size() - 1); .get(0);
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) ((Map<String, Object>) ((Map<String, Object>) trace Map<String, Object> map = (Map<String, Object>) ((Map<String, Object>) ((Map<String, Object>) trace
.get("info")).get("headers")).get("response"); .get("info")).get("headers")).get("response");
assertThat(map.get("status")).isEqualTo("200"); assertThat(map.get("status")).isEqualTo("200");
} }
@Test @Test
@SuppressWarnings("unchecked")
public void traceWithParameterMap() throws Exception { public void traceWithParameterMap() throws Exception {
this.restTemplate.withBasicAuth("user", getPassword()) this.restTemplate.withBasicAuth("user", getPassword())
.getForEntity("/application/health?param1=value1", String.class); .getForEntity("/application/health?param1=value1", String.class);
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
ResponseEntity<List> entity = this.restTemplate ResponseEntity<Map> entity = this.restTemplate
.withBasicAuth("user", getPassword()) .withBasicAuth("user", getPassword())
.getForEntity("/application/trace", List.class); .getForEntity("/application/trace", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@SuppressWarnings("unchecked") Map<String, Object> body = entity.getBody();
List<Map<String, Object>> list = entity.getBody(); Map<String, Object> trace = ((List<Map<String, Object>>) body.get("traces"))
Map<String, Object> trace = list.get(0); .get(0);
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) ((Map<String, Object>) trace Map<String, Object> map = (Map<String, Object>) ((Map<String, Object>) trace
.get("info")).get("parameters"); .get("info")).get("parameters");
assertThat(map.get("param1")).isNotNull(); assertThat(map.get("param1")).isNotNull();
......
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