spring mvc tests to java (#11114)

Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
This commit is contained in:
Andrei Chugunov 2024-05-08 12:28:35 +03:00 committed by GitHub
parent 32df5ae710
commit deac3971d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 1398 additions and 1162 deletions

View File

@ -1,61 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package test.boot
import boot.SavingAuthenticationProvider
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
SavingAuthenticationProvider savingAuthenticationProvider() {
return new SavingAuthenticationProvider()
}
/**
* Following configuration is required for unauthorised call tests (form would redirect, we need 401)
*/
@Configuration
@Order(1)
static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.antMatcher("/basicsecured/**")
.authorizeRequests()
.antMatchers("/basicsecured/**").authenticated()
.and()
.httpBasic()
.and().authenticationProvider(applicationContext.getBean(SavingAuthenticationProvider))
}
}
/**
* Following configuration is required in order to get form login, needed by password tests
*/
@Configuration
static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/formsecured/**").authenticated()
.and()
.formLogin()
.and().authenticationProvider(applicationContext.getBean(SavingAuthenticationProvider))
}
}
}

View File

@ -1,20 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package test.boot
import boot.AbstractSpringBootBasedTest
class SpringBootBasedTest extends AbstractSpringBootBasedTest {
Class<?> securityConfigClass() {
SecurityConfig
}
@Override
int getResponseCodeOnNonStandardHttpMethod() {
Boolean.getBoolean("testLatestDeps") ? 500 : 200
}
}

View File

@ -1,90 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package test.filter
import io.opentelemetry.instrumentation.test.base.HttpServerTest
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.FilterConfig
import javax.servlet.ServletException
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
@Configuration
class ServletFilterConfig {
@Bean
Filter servletFilter() {
return new Filter() {
@Override
void init(FilterConfig filterConfig) throws ServletException {
}
@Override
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request
HttpServletResponse resp = (HttpServletResponse) response
ServerEndpoint endpoint = ServerEndpoint.forPath(req.servletPath)
HttpServerTest.controller(endpoint) {
resp.contentType = "text/plain"
switch (endpoint) {
case SUCCESS:
resp.status = endpoint.status
resp.writer.print(endpoint.body)
break
case QUERY_PARAM:
resp.status = endpoint.status
resp.writer.print(req.queryString)
break
case PATH_PARAM:
resp.status = endpoint.status
resp.writer.print(endpoint.body)
break
case REDIRECT:
resp.sendRedirect(endpoint.body)
break
case CAPTURE_HEADERS:
resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request"))
resp.status = endpoint.status
resp.writer.print(endpoint.body)
break
case ERROR:
resp.sendError(endpoint.status, endpoint.body)
break
case EXCEPTION:
throw new Exception(endpoint.body)
case INDEXED_CHILD:
INDEXED_CHILD.collectSpanAttributes { name -> req.getParameter(name) }
resp.writer.print(endpoint.body)
break
default:
chain.doFilter(request, response)
}
}
}
@Override
void destroy() {
}
}
}
}

View File

@ -1,25 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package test.filter
import filter.AbstractServletFilterTest
import test.boot.SecurityConfig
class ServletFilterTest extends AbstractServletFilterTest {
Class<?> securityConfigClass() {
SecurityConfig
}
Class<?> filterConfigClass() {
ServletFilterConfig
}
@Override
int getResponseCodeOnNonStandardHttpMethod() {
Boolean.getBoolean("testLatestDeps") ? 500 : 200
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v3_1.boot;
import io.opentelemetry.instrumentation.spring.webmvc.boot.SavingAuthenticationProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SavingAuthenticationProvider savingAuthenticationProvider() {
return new SavingAuthenticationProvider();
}
/**
* Following configuration is required for unauthorised call tests (form would redirect, we need
* 401)
*/
@Configuration
@Order(1)
static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.antMatcher("/basicsecured/**")
.authorizeRequests()
.antMatchers("/basicsecured/**")
.authenticated()
.and()
.httpBasic()
.and()
.authenticationProvider(
getApplicationContext().getBean(SavingAuthenticationProvider.class));
}
}
/** Following configuration is required in order to get form login, needed by password tests */
@Configuration
static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/formsecured/**")
.authenticated()
.and()
.formLogin()
.and()
.authenticationProvider(
getApplicationContext().getBean(SavingAuthenticationProvider.class));
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v3_1.boot;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION;
import com.google.common.collect.ImmutableMap;
import io.opentelemetry.instrumentation.spring.webmvc.boot.AbstractSpringBootBasedTest;
import io.opentelemetry.instrumentation.spring.webmvc.boot.AppConfig;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
class SpringBootBasedTest extends AbstractSpringBootBasedTest {
@RegisterExtension
private static final InstrumentationExtension testing =
HttpServerInstrumentationExtension.forAgent();
private ConfigurableApplicationContext context;
@Override
protected ConfigurableApplicationContext context() {
return context;
}
@Override
protected ConfigurableApplicationContext setupServer() {
SpringApplication app = new SpringApplication(AppConfig.class, securityConfigClass());
app.setDefaultProperties(
ImmutableMap.of(
"server.port",
port,
"server.context-path",
getContextPath(),
"server.servlet.contextPath",
getContextPath(),
"server.error.include-message",
"always"));
context = app.run();
return context;
}
@Override
public Class<?> securityConfigClass() {
return SecurityConfig.class;
}
@Override
protected void configure(HttpServerTestOptions options) {
super.configure(options);
options.setResponseCodeOnNonStandardHttpMethod(
Boolean.getBoolean("testLatestDeps") ? 500 : 200);
options.setExpectedException(new RuntimeException(EXCEPTION.getBody()));
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v3_1.filter;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD;
import io.opentelemetry.instrumentation.test.base.HttpServerTest;
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class ServletFilterConfig {
@Bean
Filter servletFilter() {
return new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
ServerEndpoint endpoint = ServerEndpoint.forPath(req.getServletPath());
HttpServerTest.controller(
endpoint,
() -> {
resp.setContentType("text/plain");
switch (endpoint.name()) {
case "SUCCESS":
resp.setStatus(endpoint.getStatus());
resp.getWriter().print(endpoint.getBody());
break;
case "QUERY_PARAM":
resp.setStatus(endpoint.getStatus());
resp.getWriter().print(req.getQueryString());
break;
case "PATH_PARAM":
resp.setStatus(endpoint.getStatus());
resp.getWriter().print(endpoint.getBody());
break;
case "REDIRECT":
resp.sendRedirect(endpoint.getBody());
break;
case "CAPTURE_HEADERS":
resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request"));
resp.setStatus(endpoint.getStatus());
resp.getWriter().print(endpoint.getBody());
break;
case "ERROR":
resp.sendError(endpoint.getStatus(), endpoint.getBody());
break;
case "EXCEPTION":
throw new Exception(endpoint.getBody());
case "INDEXED_CHILD":
INDEXED_CHILD.collectSpanAttributes(req::getParameter);
resp.getWriter().print(endpoint.getBody());
break;
default:
chain.doFilter(request, response);
}
return null;
});
}
@Override
public void destroy() {}
};
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v3_1.filter;
import com.google.common.collect.ImmutableMap;
import io.opentelemetry.instrumentation.spring.webmvc.filter.AbstractServletFilterTest;
import io.opentelemetry.instrumentation.spring.webmvc.filter.FilteredAppConfig;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
import io.opentelemetry.javaagent.instrumentation.spring.webmvc.v3_1.boot.SecurityConfig;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
class ServletFilterTest extends AbstractServletFilterTest {
@RegisterExtension
private static final InstrumentationExtension testing =
HttpServerInstrumentationExtension.forAgent();
@Override
protected Class<?> securityConfigClass() {
return SecurityConfig.class;
}
@Override
protected Class<?> filterConfigClass() {
return ServletFilterConfig.class;
}
@Override
protected ConfigurableApplicationContext setupServer() {
SpringApplication app =
new SpringApplication(FilteredAppConfig.class, securityConfigClass(), filterConfigClass());
app.setDefaultProperties(
ImmutableMap.of("server.port", port, "server.error.include-message", "always"));
return app.run();
}
@Override
protected void configure(HttpServerTestOptions options) {
super.configure(options);
options.setResponseCodeOnNonStandardHttpMethod(
Boolean.getBoolean("testLatestDeps") ? 500 : 200);
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import boot.SavingAuthenticationProvider
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
SavingAuthenticationProvider savingAuthenticationProvider() {
return new SavingAuthenticationProvider()
}
/**
* Following configuration is required for unauthorised call tests (form would redirect, we need 401)
*/
@Bean
@Order(1)
SecurityFilterChain apiWebSecurity(HttpSecurity http, SavingAuthenticationProvider savingAuthenticationProvider) {
return http
.csrf().disable()
.securityMatcher("/basicsecured/**")
.authorizeHttpRequests()
.requestMatchers("/basicsecured/**").authenticated()
.and()
.httpBasic()
.and()
.authenticationProvider(savingAuthenticationProvider)
.build()
}
/**
* Following configuration is required in order to get form login, needed by password tests
*/
@Bean
SecurityFilterChain formLoginWebSecurity(HttpSecurity http, SavingAuthenticationProvider savingAuthenticationProvider) {
return http
.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/formsecured/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.and()
.authenticationProvider(savingAuthenticationProvider)
.build()
}
}

View File

@ -1,88 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import io.opentelemetry.instrumentation.test.base.HttpServerTest
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import jakarta.servlet.Filter
import jakarta.servlet.FilterChain
import jakarta.servlet.FilterConfig
import jakarta.servlet.ServletException
import jakarta.servlet.ServletRequest
import jakarta.servlet.ServletResponse
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
@Configuration
class ServletFilterConfig {
@Bean
Filter servletFilter() {
return new Filter() {
@Override
void init(FilterConfig filterConfig) throws ServletException {
}
@Override
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request
HttpServletResponse resp = (HttpServletResponse) response
ServerEndpoint endpoint = ServerEndpoint.forPath(req.servletPath)
HttpServerTest.controller(endpoint) {
resp.contentType = "text/plain"
switch (endpoint) {
case SUCCESS:
resp.status = endpoint.status
resp.writer.print(endpoint.body)
break
case QUERY_PARAM:
resp.status = endpoint.status
resp.writer.print(req.queryString)
break
case PATH_PARAM:
resp.status = endpoint.status
resp.writer.print(endpoint.body)
break
case REDIRECT:
resp.sendRedirect(endpoint.body)
break
case CAPTURE_HEADERS:
resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request"))
resp.status = endpoint.status
resp.writer.print(endpoint.body)
break
case ERROR:
resp.sendError(endpoint.status, endpoint.body)
break
case EXCEPTION:
throw new Exception(endpoint.body)
case INDEXED_CHILD:
INDEXED_CHILD.collectSpanAttributes { name -> req.getParameter(name) }
resp.writer.print(endpoint.body)
break
default:
chain.doFilter(request, response)
}
}
}
@Override
void destroy() {
}
}
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import filter.AbstractServletFilterTest
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.api.trace.StatusCode
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
import io.opentelemetry.sdk.trace.data.SpanData
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
class ServletFilterTest extends AbstractServletFilterTest {
Class<?> securityConfigClass() {
SecurityConfig
}
Class<?> filterConfigClass() {
ServletFilterConfig
}
@Override
void handlerSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint) {
if (Boolean.getBoolean("testLatestDeps") && endpoint == ServerEndpoint.NOT_FOUND) {
trace.span(index) {
name "ResourceHttpRequestHandler.handleRequest"
kind INTERNAL
childOf((SpanData) parent)
status StatusCode.ERROR
errorEventWithAnyMessage Class.forName("org.springframework.web.servlet.resource.NoResourceFoundException")
}
} else {
super.handlerSpan(trace, index, parent, method, endpoint)
}
}
@Override
void responseSpan(TraceAssert trace, int index, Object parent, String method, ServerEndpoint endpoint) {
if (Boolean.getBoolean("testLatestDeps") && endpoint == ServerEndpoint.NOT_FOUND) {
trace.span(index) {
name ~/\.sendError$/
kind SpanKind.INTERNAL
// not verifying the parent span, in the latest version the responseSpan is the child of the SERVER span, not the handler span
}
} else {
super.responseSpan(trace, index, parent, method, endpoint)
}
}
@Override
int getResponseCodeOnNonStandardHttpMethod() {
400
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import boot.AbstractSpringBootBasedTest
import io.opentelemetry.api.trace.StatusCode
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
import io.opentelemetry.sdk.trace.data.SpanData
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
class SpringBootBasedTest extends AbstractSpringBootBasedTest {
Class<?> securityConfigClass() {
SecurityConfig
}
@Override
void handlerSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint) {
if (Boolean.getBoolean("testLatestDeps") && endpoint == ServerEndpoint.NOT_FOUND) {
trace.span(index) {
name "ResourceHttpRequestHandler.handleRequest"
kind INTERNAL
childOf((SpanData) parent)
status StatusCode.ERROR
errorEventWithAnyMessage Class.forName("org.springframework.web.servlet.resource.NoResourceFoundException")
}
} else {
super.handlerSpan(trace, index, parent, method, endpoint)
}
}
@Override
int getResponseCodeOnNonStandardHttpMethod() {
400
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0.boot;
import io.opentelemetry.instrumentation.spring.webmvc.boot.SavingAuthenticationProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SavingAuthenticationProvider savingAuthenticationProvider() {
return new SavingAuthenticationProvider();
}
/**
* Following configuration is required for unauthorised call tests (form would redirect, we need
* 401)
*/
@Bean
@Order(1)
SecurityFilterChain apiWebSecurity(
HttpSecurity http, SavingAuthenticationProvider savingAuthenticationProvider)
throws Exception {
return http.csrf(AbstractHttpConfigurer::disable)
.securityMatcher("/basicsecured/**")
.authorizeHttpRequests(auth -> auth.requestMatchers("/basicsecured/**").authenticated())
.authenticationProvider(savingAuthenticationProvider)
.httpBasic(Customizer.withDefaults())
.build();
}
/** Following configuration is required in order to get form login, needed by password tests */
@Bean
SecurityFilterChain formLoginWebSecurity(
HttpSecurity http, SavingAuthenticationProvider savingAuthenticationProvider)
throws Exception {
return http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(
auth ->
auth.requestMatchers("/formsecured/**").authenticated().anyRequest().permitAll())
.authenticationProvider(savingAuthenticationProvider)
.formLogin(Customizer.withDefaults())
.build();
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0.boot;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableMap;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.spring.webmvc.boot.AbstractSpringBootBasedTest;
import io.opentelemetry.instrumentation.spring.webmvc.boot.AppConfig;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
class SpringBootBasedTest extends AbstractSpringBootBasedTest {
@RegisterExtension
private static final InstrumentationExtension testing =
HttpServerInstrumentationExtension.forAgent();
private static final boolean testLatestDeps = Boolean.getBoolean("testLatestDeps");
private ConfigurableApplicationContext context;
@Override
protected ConfigurableApplicationContext context() {
return context;
}
@Override
protected Class<?> securityConfigClass() {
return SecurityConfig.class;
}
@Override
protected ConfigurableApplicationContext setupServer() {
SpringApplication app = new SpringApplication(AppConfig.class, securityConfigClass());
app.setDefaultProperties(
ImmutableMap.of(
"server.port",
port,
"server.context-path",
getContextPath(),
"server.servlet.contextPath",
getContextPath(),
"server.error.include-message",
"always"));
context = app.run();
return context;
}
@Override
protected SpanDataAssert assertHandlerSpan(
SpanDataAssert span, String method, ServerEndpoint endpoint) {
if (testLatestDeps && endpoint == ServerEndpoint.NOT_FOUND) {
String handlerSpanName = "ResourceHttpRequestHandler.handleRequest";
span.hasName(handlerSpanName)
.hasKind(SpanKind.INTERNAL)
.hasStatus(StatusData.error())
.hasEventsSatisfyingExactly(
event ->
event
.hasName("exception")
.hasAttributesSatisfyingExactly(
equalTo(
EXCEPTION_TYPE,
"org.springframework.web.servlet.resource.NoResourceFoundException"),
satisfies(EXCEPTION_MESSAGE, val -> assertThat(val).isNotNull()),
satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class))));
return span;
} else {
return super.assertHandlerSpan(span, method, endpoint);
}
}
@Override
protected SpanDataAssert assertResponseSpan(
SpanDataAssert span, SpanData parentSpan, String method, ServerEndpoint endpoint) {
if (testLatestDeps && endpoint == ServerEndpoint.NOT_FOUND) {
// not verifying the parent span, in the latest version the responseSpan is the child of the
// SERVER span, not the handler span
return super.assertResponseSpan(span, method, endpoint);
} else {
return super.assertResponseSpan(span, parentSpan, method, endpoint);
}
}
@Override
protected void configure(HttpServerTestOptions options) {
super.configure(options);
options.setResponseCodeOnNonStandardHttpMethod(400);
options.setExpectedException(new RuntimeException(EXCEPTION.getBody()));
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0.filter;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD;
import io.opentelemetry.instrumentation.test.base.HttpServerTest;
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class ServletFilterConfig {
@Bean
Filter servletFilter() {
return new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
ServerEndpoint endpoint = ServerEndpoint.forPath(req.getServletPath());
HttpServerTest.controller(
endpoint,
() -> {
resp.setContentType("text/plain");
switch (endpoint.name()) {
case "SUCCESS":
resp.setStatus(endpoint.getStatus());
resp.getWriter().print(endpoint.getBody());
break;
case "QUERY_PARAM":
resp.setStatus(endpoint.getStatus());
resp.getWriter().print(req.getQueryString());
break;
case "PATH_PARAM":
resp.setStatus(endpoint.getStatus());
resp.getWriter().print(endpoint.getBody());
break;
case "REDIRECT":
resp.sendRedirect(endpoint.getBody());
break;
case "CAPTURE_HEADERS":
resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request"));
resp.setStatus(endpoint.getStatus());
resp.getWriter().print(endpoint.getBody());
break;
case "ERROR":
resp.sendError(endpoint.getStatus(), endpoint.getBody());
break;
case "EXCEPTION":
throw new Exception(endpoint.getBody());
case "INDEXED_CHILD":
INDEXED_CHILD.collectSpanAttributes(req::getParameter);
resp.getWriter().print(endpoint.getBody());
break;
default:
chain.doFilter(request, response);
}
return null;
});
}
@Override
public void destroy() {}
};
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0.filter;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableMap;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.spring.webmvc.filter.AbstractServletFilterTest;
import io.opentelemetry.instrumentation.spring.webmvc.filter.FilteredAppConfig;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
import io.opentelemetry.javaagent.instrumentation.spring.webmvc.v6_0.boot.SecurityConfig;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.data.StatusData;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
class ServletFilterTest extends AbstractServletFilterTest {
private static final boolean testLatestDeps = Boolean.getBoolean("testLatestDeps");
@RegisterExtension
private static final InstrumentationExtension testing =
HttpServerInstrumentationExtension.forAgent();
@Override
protected Class<?> securityConfigClass() {
return SecurityConfig.class;
}
@Override
protected Class<?> filterConfigClass() {
return ServletFilterConfig.class;
}
@Override
protected ConfigurableApplicationContext setupServer() {
SpringApplication app =
new SpringApplication(FilteredAppConfig.class, securityConfigClass(), filterConfigClass());
app.setDefaultProperties(
ImmutableMap.of("server.port", port, "server.error.include-message", "always"));
return app.run();
}
@Override
protected SpanDataAssert assertHandlerSpan(
SpanDataAssert span, String method, ServerEndpoint endpoint) {
if (testLatestDeps && endpoint == ServerEndpoint.NOT_FOUND) {
String handlerSpanName = "ResourceHttpRequestHandler.handleRequest";
span.hasName(handlerSpanName)
.hasKind(SpanKind.INTERNAL)
.hasStatus(StatusData.error())
.hasEventsSatisfyingExactly(
event ->
event
.hasName("exception")
.hasAttributesSatisfyingExactly(
equalTo(
EXCEPTION_TYPE,
"org.springframework.web.servlet.resource.NoResourceFoundException"),
satisfies(EXCEPTION_MESSAGE, val -> assertThat(val).isNotNull()),
satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class))));
return span;
} else {
return super.assertHandlerSpan(span, method, endpoint);
}
}
@Override
protected SpanDataAssert assertResponseSpan(
SpanDataAssert span, SpanData parentSpan, String method, ServerEndpoint endpoint) {
if (testLatestDeps && endpoint == ServerEndpoint.NOT_FOUND) {
span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendError"));
span.hasKind(SpanKind.INTERNAL);
// not verifying the parent span, in the latest version the responseSpan is the child of the
// SERVER span, not the handler span
return span;
} else {
return super.assertResponseSpan(span, parentSpan, method, endpoint);
}
}
@Override
protected void configure(HttpServerTestOptions options) {
super.configure(options);
options.setResponseCodeOnNonStandardHttpMethod(400);
}
}

View File

@ -5,8 +5,6 @@ plugins {
dependencies {
implementation(project(":testing-common"))
implementation("org.spockframework:spock-spring")
compileOnly("org.springframework.boot:spring-boot-starter-test:1.5.17.RELEASE")
compileOnly("org.springframework.boot:spring-boot-starter-web:1.5.17.RELEASE")
compileOnly("org.springframework.boot:spring-boot-starter-security:1.5.17.RELEASE")

View File

@ -1,212 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package boot
import io.opentelemetry.api.trace.StatusCode
import io.opentelemetry.instrumentation.api.internal.HttpConstants
import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
import io.opentelemetry.sdk.trace.data.SpanData
import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes
import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest
import io.opentelemetry.testing.internal.armeria.common.HttpData
import io.opentelemetry.testing.internal.armeria.common.MediaType
import io.opentelemetry.testing.internal.armeria.common.QueryParams
import org.springframework.boot.SpringApplication
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.security.web.util.OnCommittedResponseWrapper
import org.springframework.web.servlet.view.RedirectView
import java.util.regex.Pattern
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_ERROR
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.LOGIN
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
abstract class AbstractSpringBootBasedTest extends HttpServerTest<ConfigurableApplicationContext> implements AgentTestTrait {
abstract Class<?> securityConfigClass()
@Override
ConfigurableApplicationContext startServer(int port) {
def app = new SpringApplication(AppConfig, securityConfigClass())
app.setDefaultProperties([
"server.port" : port,
"server.context-path" : getContextPath(),
"server.servlet.contextPath" : getContextPath(),
"server.error.include-message": "always",
])
def context = app.run()
return context
}
@Override
void stopServer(ConfigurableApplicationContext ctx) {
ctx.close()
}
@Override
String getContextPath() {
return "/xyz"
}
@Override
boolean hasHandlerSpan(ServerEndpoint endpoint) {
true
}
@Override
boolean hasRenderSpan(ServerEndpoint endpoint) {
endpoint == REDIRECT
}
@Override
boolean hasResponseSpan(ServerEndpoint endpoint) {
endpoint == REDIRECT || endpoint == NOT_FOUND
}
@Override
boolean testPathParam() {
true
}
@Override
boolean hasErrorPageSpans(ServerEndpoint endpoint) {
endpoint == NOT_FOUND
}
@Override
String expectedHttpRoute(ServerEndpoint endpoint, String method) {
if (method == HttpConstants._OTHER) {
return getContextPath() + endpoint.path
}
switch (endpoint) {
case PATH_PARAM:
return getContextPath() + "/path/{id}/param"
case NOT_FOUND:
return getContextPath() + "/**"
case LOGIN:
return getContextPath() + "/*"
default:
return super.expectedHttpRoute(endpoint, method)
}
}
def "test spans with auth error"() {
setup:
def authProvider = server.getBean(SavingAuthenticationProvider)
def request = request(AUTH_ERROR, "GET")
when:
authProvider.latestAuthentications.clear()
def response = client.execute(request).aggregate().join()
then:
response.status().code() == 401 // not secured
and:
assertTraces(1) {
trace(0, 3) {
serverSpan(it, 0, null, null, "GET", AUTH_ERROR)
sendErrorSpan(it, 1, span(0))
errorPageSpans(it, 2, null)
}
}
}
def "test character encoding of #testPassword"() {
setup:
def authProvider = server.getBean(SavingAuthenticationProvider)
QueryParams form = QueryParams.of("username", "test", "password", testPassword)
def request = AggregatedHttpRequest.of(
request(LOGIN, "POST").headers().toBuilder().contentType(MediaType.FORM_DATA).build(),
HttpData.ofUtf8(form.toQueryString()))
when:
authProvider.latestAuthentications.clear()
def response = client.execute(request).aggregate().join()
then:
response.status().code() == 302 // redirect after success
authProvider.latestAuthentications.get(0).password == testPassword
and:
assertTraces(1) {
trace(0, 2) {
serverSpan(it, 0, null, null, "POST", LOGIN)
redirectSpan(it, 1, span(0))
}
}
where:
testPassword << ["password", "dfsdföääöüüä", "🤓"]
}
@Override
void errorPageSpans(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
name "BasicErrorController.error"
kind INTERNAL
attributes {
}
}
}
@Override
void responseSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
def methodName = endpoint == NOT_FOUND ? "sendError" : "sendRedirect"
// the response wrapper class names vary depending on spring version & scenario
def namePattern = Pattern.compile("(OnCommittedResponseWrapper|HttpServletResponseWrapper|FirewalledResponse).$methodName")
trace.span(index) {
name namePattern
kind INTERNAL
attributes {
"$CodeIncubatingAttributes.CODE_NAMESPACE" {
it == OnCommittedResponseWrapper.name
|| it == "org.springframework.security.web.firewall.FirewalledResponse"
|| it == "jakarta.servlet.http.HttpServletResponseWrapper"
}
"$CodeIncubatingAttributes.CODE_FUNCTION" methodName
}
}
}
@Override
void renderSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
name "Render RedirectView"
kind INTERNAL
attributes {
"spring-webmvc.view.type" RedirectView.name
}
}
}
@Override
void handlerSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
def handlerSpanName = "TestController.${endpoint.name().toLowerCase()}"
if (endpoint == NOT_FOUND) {
handlerSpanName = "ResourceHttpRequestHandler.handleRequest"
}
trace.span(index) {
name handlerSpanName
kind INTERNAL
if (endpoint == EXCEPTION) {
status StatusCode.ERROR
errorEvent(Exception, EXCEPTION.body)
}
childOf((SpanData) parent)
}
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package boot
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider
import org.springframework.security.core.AuthenticationException
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
class SavingAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
List<TestUserDetails> latestAuthentications = new ArrayList<>()
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
// none
}
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
def details = new TestUserDetails(username, authentication.credentials.toString())
latestAuthentications.add(details)
return details
}
}
class TestUserDetails implements UserDetails {
private final String username
private final String password
TestUserDetails(String username, String password) {
this.username = username
this.password = password
}
@Override
Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptySet()
}
@Override
String getPassword() {
return password
}
@Override
String getUsername() {
return username
}
@Override
boolean isAccountNonExpired() {
return true
}
@Override
boolean isAccountNonLocked() {
return true
}
@Override
boolean isCredentialsNonExpired() {
return true
}
@Override
boolean isEnabled() {
return true
}
}

View File

@ -1,108 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package boot
import io.opentelemetry.instrumentation.test.base.HttpServerTest
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.servlet.view.RedirectView
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
@Controller
class TestController {
@RequestMapping("/basicsecured/endpoint")
@ResponseBody
String secureEndpoint() {
HttpServerTest.controller(SUCCESS) {
SUCCESS.body
}
}
@RequestMapping("/success")
@ResponseBody
String success() {
HttpServerTest.controller(SUCCESS) {
SUCCESS.body
}
}
@RequestMapping("/query")
@ResponseBody
String query_param(@RequestParam("some") String param) {
HttpServerTest.controller(QUERY_PARAM) {
"some=$param"
}
}
@RequestMapping("/redirect")
@ResponseBody
RedirectView redirect() {
HttpServerTest.controller(REDIRECT) {
new RedirectView(REDIRECT.body)
}
}
@RequestMapping("/error-status")
ResponseEntity error() {
HttpServerTest.controller(ERROR) {
new ResponseEntity(ERROR.body, HttpStatus.valueOf(ERROR.status))
}
}
@RequestMapping("/exception")
ResponseEntity exception() {
HttpServerTest.controller(EXCEPTION) {
throw new Exception(EXCEPTION.body)
}
}
@RequestMapping("/captureHeaders")
ResponseEntity capture_headers(@RequestHeader("X-Test-Request") String testRequestHeader) {
HttpServerTest.controller(CAPTURE_HEADERS) {
ResponseEntity.ok()
.header("X-Test-Response", testRequestHeader)
.body(CAPTURE_HEADERS.body)
}
}
@RequestMapping("/path/{id}/param")
@ResponseBody
String path_param(@PathVariable("id") int id) {
HttpServerTest.controller(PATH_PARAM) {
id
}
}
@RequestMapping("/child")
@ResponseBody
String indexed_child(@RequestParam("id") String id) {
HttpServerTest.controller(INDEXED_CHILD) {
INDEXED_CHILD.collectSpanAttributes { it == "id" ? id : null }
INDEXED_CHILD.body
}
}
@ExceptionHandler
ResponseEntity handleException(Throwable throwable) {
new ResponseEntity(throwable.message, HttpStatus.INTERNAL_SERVER_ERROR)
}
}

View File

@ -1,111 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package filter
import io.opentelemetry.instrumentation.api.internal.HttpConstants
import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
import io.opentelemetry.sdk.trace.data.SpanData
import org.springframework.boot.SpringApplication
import org.springframework.context.ConfigurableApplicationContext
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
abstract class AbstractServletFilterTest extends HttpServerTest<ConfigurableApplicationContext> implements AgentTestTrait {
abstract Class<?> securityConfigClass()
abstract Class<?> filterConfigClass()
@Override
ConfigurableApplicationContext startServer(int port) {
def app = new SpringApplication(FilteredAppConfig, securityConfigClass(), filterConfigClass())
app.setDefaultProperties(["server.port": port, "server.error.include-message": "always"])
def context = app.run()
return context
}
@Override
void stopServer(ConfigurableApplicationContext ctx) {
ctx.close()
}
@Override
boolean hasHandlerSpan(ServerEndpoint endpoint) {
endpoint == NOT_FOUND
}
@Override
boolean hasErrorPageSpans(ServerEndpoint endpoint) {
endpoint == ERROR || endpoint == EXCEPTION || endpoint == NOT_FOUND
}
@Override
boolean hasResponseSpan(ServerEndpoint endpoint) {
endpoint == REDIRECT || endpoint == ERROR || endpoint == NOT_FOUND
}
@Override
void responseSpan(TraceAssert trace, int index, Object parent, String method, ServerEndpoint endpoint) {
switch (endpoint) {
case REDIRECT:
redirectSpan(trace, index, parent)
break
case ERROR:
case NOT_FOUND:
sendErrorSpan(trace, index, parent)
break
}
}
@Override
boolean testPathParam() {
true
}
@Override
void handlerSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint) {
trace.span(index) {
name "ResourceHttpRequestHandler.handleRequest"
kind INTERNAL
childOf((SpanData) parent)
}
}
@Override
String expectedHttpRoute(ServerEndpoint endpoint, String method) {
if (method == HttpConstants._OTHER) {
return getContextPath() + endpoint.path
}
switch (endpoint) {
case PATH_PARAM:
return getContextPath() + "/path/{id}/param"
case NOT_FOUND:
return getContextPath() + "/**"
default:
return super.expectedHttpRoute(endpoint, method)
}
}
@Override
void errorPageSpans(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
name "BasicErrorController.error"
kind INTERNAL
childOf((SpanData) parent)
attributes {
}
}
}
}

View File

@ -1,132 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package filter
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.Bean
import org.springframework.format.FormatterRegistry
import org.springframework.http.HttpInputMessage
import org.springframework.http.HttpOutputMessage
import org.springframework.http.MediaType
import org.springframework.http.converter.AbstractHttpMessageConverter
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.http.converter.HttpMessageNotReadableException
import org.springframework.http.converter.HttpMessageNotWritableException
import org.springframework.util.StreamUtils
import org.springframework.validation.MessageCodesResolver
import org.springframework.validation.Validator
import org.springframework.web.HttpMediaTypeNotAcceptableException
import org.springframework.web.accept.ContentNegotiationStrategy
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.method.support.HandlerMethodArgumentResolver
import org.springframework.web.method.support.HandlerMethodReturnValueHandler
import org.springframework.web.servlet.HandlerExceptionResolver
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import java.nio.charset.StandardCharsets
@SpringBootApplication
class FilteredAppConfig implements WebMvcConfigurer {
@Override
void configurePathMatch(PathMatchConfigurer configurer) {}
@Override
void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.favorParameter(true)
.ignoreAcceptHeader(true)
.useJaf(false)
.defaultContentTypeStrategy(new ContentNegotiationStrategy() {
@Override
List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
return [MediaType.TEXT_PLAIN]
}
})
}
@Override
void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
@Override
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
@Override
void addFormatters(FormatterRegistry registry) {}
@Override
void addInterceptors(InterceptorRegistry registry) {}
@Override
void addResourceHandlers(ResourceHandlerRegistry registry) {}
@Override
void addCorsMappings(CorsRegistry registry) {}
@Override
void addViewControllers(ViewControllerRegistry registry) {}
@Override
void configureViewResolvers(ViewResolverRegistry registry) {}
@Override
void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {}
@Override
void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {}
@Override
void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
@Override
void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
@Override
void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {}
@Override
void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {}
@Override
Validator getValidator() {
return null
}
@Override
MessageCodesResolver getMessageCodesResolver() {
return null
}
@Bean
HttpMessageConverter<Map<String, Object>> createPlainMapMessageConverter() {
return new AbstractHttpMessageConverter<Map<String, Object>>(MediaType.TEXT_PLAIN) {
@Override
protected boolean supports(Class<?> clazz) {
return Map.isAssignableFrom(clazz)
}
@Override
protected Map<String, Object> readInternal(Class<? extends Map<String, Object>> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null
}
@Override
protected void writeInternal(Map<String, Object> stringObjectMap, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
StreamUtils.copy(stringObjectMap.get("message") as String, StandardCharsets.UTF_8, outputMessage.getBody())
}
}
}
}

View File

@ -1,76 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package filter
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.servlet.view.RedirectView
/**
* None of the methods in this controller should be called because they are intercepted
* by the filter
*/
@Controller
class TestController {
@RequestMapping("/success")
@ResponseBody
String success() {
throw new Exception("This should not be called")
}
@RequestMapping("/query")
@ResponseBody
String query_param(@RequestParam("some") String param) {
throw new Exception("This should not be called")
}
@RequestMapping("/path/{id}/param")
@ResponseBody
String path_param(@PathVariable Integer id) {
throw new Exception("This should not be called")
}
@RequestMapping("/redirect")
@ResponseBody
RedirectView redirect() {
throw new Exception("This should not be called")
}
@RequestMapping("/error-status")
ResponseEntity error() {
throw new Exception("This should not be called")
}
@RequestMapping("/exception")
ResponseEntity exception() {
throw new Exception("This should not be called")
}
@RequestMapping("/captureHeaders")
ResponseEntity capture_headers() {
throw new Exception("This should not be called")
}
@RequestMapping("/child")
@ResponseBody
ResponseEntity indexed_child(@RequestParam("id") String id) {
throw new Exception("This should not be called")
}
@ExceptionHandler
ResponseEntity handleException(Throwable throwable) {
new ResponseEntity(throwable.message, HttpStatus.INTERNAL_SERVER_ERROR)
}
}

View File

@ -0,0 +1,229 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.webmvc.boot;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_ERROR;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.LOGIN;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION;
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest;
import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse;
import io.opentelemetry.testing.internal.armeria.common.HttpData;
import io.opentelemetry.testing.internal.armeria.common.MediaType;
import io.opentelemetry.testing.internal.armeria.common.QueryParams;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.security.web.util.OnCommittedResponseWrapper;
import org.springframework.web.servlet.view.RedirectView;
public abstract class AbstractSpringBootBasedTest
extends AbstractHttpServerTest<ConfigurableApplicationContext> {
protected abstract ConfigurableApplicationContext context();
protected abstract Class<?> securityConfigClass();
@Override
protected void stopServer(ConfigurableApplicationContext ctx) {
ctx.close();
}
@Override
protected void configure(HttpServerTestOptions options) {
super.configure(options);
options.setContextPath("/xyz");
options.setHasHandlerSpan(unused -> true);
options.setHasResponseSpan(endpoint -> endpoint == REDIRECT || endpoint == NOT_FOUND);
options.setTestPathParam(true);
options.setHasErrorPageSpans(endpoint -> endpoint == NOT_FOUND);
options.setHasRenderSpan(endpoint -> endpoint == REDIRECT);
}
@Override
public String expectedHttpRoute(ServerEndpoint endpoint, String method) {
if (HttpConstants._OTHER.equals(method)) {
return getContextPath() + endpoint.getPath();
}
switch (endpoint.name()) {
case "PATH_PARAM":
return getContextPath() + "/path/{id}/param";
case "NOT_FOUND":
return getContextPath() + "/**";
case "LOGIN":
return getContextPath() + "/*";
default:
return super.expectedHttpRoute(endpoint, method);
}
}
@Test
void testSpansWithAuthError() {
SavingAuthenticationProvider authProvider =
context().getBean(SavingAuthenticationProvider.class);
AggregatedHttpRequest request = request(AUTH_ERROR, "GET");
authProvider.latestAuthentications.clear();
AggregatedHttpResponse response = client.execute(request).aggregate().join();
assertThat(response.status().code()).isEqualTo(401); // not secured
testing()
.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> assertServerSpan(span, "GET", AUTH_ERROR, AUTH_ERROR.getStatus()),
span ->
span.satisfies(
spanData -> assertThat(spanData.getName()).endsWith(".sendError"))
.hasKind(SpanKind.INTERNAL),
span -> errorPageSpanAssertions(null, null)));
}
@ParameterizedTest
@ValueSource(strings = {"password", "dfsdföääöüüä", "🤓"})
void testCharacterEncodingOfTestPassword(String testPassword) {
SavingAuthenticationProvider authProvider =
context().getBean(SavingAuthenticationProvider.class);
QueryParams form = QueryParams.of("username", "test", "password", testPassword);
AggregatedHttpRequest request =
AggregatedHttpRequest.of(
request(LOGIN, "POST").headers().toBuilder().contentType(MediaType.FORM_DATA).build(),
HttpData.ofUtf8(form.toQueryString()));
authProvider.latestAuthentications.clear();
AggregatedHttpResponse response = client.execute(request).aggregate().join();
assertThat(response.status().code()).isEqualTo(302); // redirect after success
assertThat(authProvider.latestAuthentications.get(0).getPassword()).isEqualTo(testPassword);
testing()
.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> assertServerSpan(span, "POST", LOGIN, LOGIN.getStatus()),
span ->
span.satisfies(
spanData ->
assertThat(spanData.getName()).endsWith(".sendRedirect"))
.hasKind(SpanKind.INTERNAL)));
}
@Override
protected List<Consumer<SpanDataAssert>> errorPageSpanAssertions(
String method, ServerEndpoint endpoint) {
List<Consumer<SpanDataAssert>> spanAssertions = new ArrayList<>();
spanAssertions.add(
span ->
span.hasName("BasicErrorController.error")
.hasKind(SpanKind.INTERNAL)
.hasAttributesSatisfying(Attributes::isEmpty));
return spanAssertions;
}
@Override
protected SpanDataAssert assertResponseSpan(
SpanDataAssert span, String method, ServerEndpoint endpoint) {
String methodName = endpoint == NOT_FOUND ? "sendError" : "sendRedirect";
if (endpoint == NOT_FOUND) {
span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendError"));
} else {
span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendRedirect"));
}
span.hasKind(SpanKind.INTERNAL)
.hasAttributesSatisfyingExactly(
satisfies(
CODE_NAMESPACE,
val ->
val.satisfiesAnyOf(
v -> assertThat(v).isEqualTo(OnCommittedResponseWrapper.class.getName()),
v ->
assertThat(v)
.isEqualTo(
"org.springframework.security.web.firewall.FirewalledResponse"),
v ->
assertThat(v)
.isEqualTo("jakarta.servlet.http.HttpServletResponseWrapper"))),
equalTo(CODE_FUNCTION, methodName));
return span;
}
@Override
protected SpanDataAssert assertRenderSpan(
SpanDataAssert span, String method, ServerEndpoint endpoint) {
span.hasName("Render RedirectView")
.hasKind(SpanKind.INTERNAL)
.hasAttributesSatisfyingExactly(
equalTo(
AttributeKey.stringKey("spring-webmvc.view.type"), RedirectView.class.getName()));
return span;
}
@Override
protected SpanDataAssert assertHandlerSpan(
SpanDataAssert span, String method, ServerEndpoint endpoint) {
String handlerSpanName = getHandlerSpanName(endpoint);
if (endpoint == NOT_FOUND) {
handlerSpanName = "ResourceHttpRequestHandler.handleRequest";
}
span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL);
if (endpoint == EXCEPTION) {
span.hasStatus(StatusData.error());
span.hasEventsSatisfyingExactly(
event ->
event
.hasName("exception")
.hasAttributesSatisfyingExactly(
equalTo(EXCEPTION_TYPE, "java.lang.RuntimeException"),
equalTo(EXCEPTION_MESSAGE, EXCEPTION.getBody()),
satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class))));
}
return span;
}
private static String getHandlerSpanName(ServerEndpoint endpoint) {
if (QUERY_PARAM.equals(endpoint)) {
return "TestController.queryParam";
} else if (PATH_PARAM.equals(endpoint)) {
return "TestController.pathParam";
} else if (CAPTURE_HEADERS.equals(endpoint)) {
return "TestController.captureHeaders";
} else if (INDEXED_CHILD.equals(endpoint)) {
return "TestController.indexedChild";
}
return "TestController." + endpoint.name().toLowerCase(Locale.ROOT);
}
}

View File

@ -3,11 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
package boot
package io.opentelemetry.instrumentation.spring.webmvc.boot;
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
class AppConfig {
}
public class AppConfig {}

View File

@ -0,0 +1,34 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.webmvc.boot;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.userdetails.UserDetails;
public class SavingAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
List<TestUserDetails> latestAuthentications = new ArrayList<>();
@Override
protected void additionalAuthenticationChecks(
UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) {
// none
}
@Override
protected UserDetails retrieveUser(
String username, UsernamePasswordAuthenticationToken authentication) {
TestUserDetails details =
new TestUserDetails(username, authentication.getCredentials().toString());
latestAuthentications.add(details);
return details;
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.webmvc.boot;
import static io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest.controller;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS;
import java.util.Objects;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.view.RedirectView;
@Controller
public class TestController {
@RequestMapping("/basicsecured/endpoint")
@ResponseBody
String secureEndpoint() {
return controller(SUCCESS, SUCCESS::getBody);
}
@RequestMapping("/success")
@ResponseBody
String success() {
return controller(SUCCESS, SUCCESS::getBody);
}
@RequestMapping("/query")
@ResponseBody
String queryParam(@RequestParam("some") String param) {
return controller(QUERY_PARAM, () -> "some=" + param);
}
@RequestMapping("/redirect")
@ResponseBody
RedirectView redirect() {
return controller(REDIRECT, () -> new RedirectView(REDIRECT.getBody()));
}
@RequestMapping("/error-status")
ResponseEntity<String> error() {
return controller(
ERROR,
() ->
ResponseEntity.status(HttpStatus.valueOf(ERROR.getStatus()).value())
.body(ERROR.getBody()));
}
@SuppressWarnings("ThrowSpecificExceptions")
@RequestMapping("/exception")
ResponseEntity<String> exception() {
return controller(
EXCEPTION,
() -> {
throw new RuntimeException(EXCEPTION.getBody());
});
}
@RequestMapping("/captureHeaders")
ResponseEntity<String> captureHeaders(@RequestHeader("X-Test-Request") String testRequestHeader) {
return controller(
CAPTURE_HEADERS,
() ->
ResponseEntity.ok()
.header("X-Test-Response", testRequestHeader)
.body(CAPTURE_HEADERS.getBody()));
}
@RequestMapping("/path/{id}/param")
@ResponseBody
String pathParam(@PathVariable("id") int id) {
return controller(PATH_PARAM, () -> String.valueOf(id));
}
@RequestMapping("/child")
@ResponseBody
String indexedChild(@RequestParam("id") String id) {
return controller(
INDEXED_CHILD,
() -> {
INDEXED_CHILD.collectSpanAttributes(it -> Objects.equals(it, "id") ? id : null);
return INDEXED_CHILD.getBody();
});
}
@ExceptionHandler
ResponseEntity<String> handleException(Throwable throwable) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.body(throwable.getMessage());
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.webmvc.boot;
import java.util.Collection;
import java.util.Collections;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class TestUserDetails implements UserDetails {
private static final long serialVersionUID = 6470776949615799570L;
private final String username;
private final String password;
TestUserDetails(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptySet();
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.webmvc.filter;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
import org.springframework.context.ConfigurableApplicationContext;
public abstract class AbstractServletFilterTest
extends AbstractHttpServerTest<ConfigurableApplicationContext> {
protected abstract Class<?> securityConfigClass();
protected abstract Class<?> filterConfigClass();
@Override
protected void stopServer(ConfigurableApplicationContext ctx) {
ctx.close();
}
@Override
protected void configure(HttpServerTestOptions options) {
super.configure(options);
options.setHasHandlerSpan(endpoint -> endpoint == NOT_FOUND);
options.setHasErrorPageSpans(
endpoint -> endpoint == ERROR || endpoint == EXCEPTION || endpoint == NOT_FOUND);
options.setHasResponseSpan(
endpoint -> endpoint == REDIRECT || endpoint == ERROR || endpoint == NOT_FOUND);
options.setTestPathParam(true);
}
@Override
protected SpanDataAssert assertResponseSpan(
SpanDataAssert span, String method, ServerEndpoint endpoint) {
if (endpoint == REDIRECT) {
span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendRedirect"));
} else if (endpoint == ERROR || endpoint == NOT_FOUND) {
span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendError"));
}
span.hasKind(SpanKind.INTERNAL);
return span;
}
@Override
protected SpanDataAssert assertHandlerSpan(
SpanDataAssert span, String method, ServerEndpoint endpoint) {
String handlerSpanName = getHandlerSpanName(endpoint);
if (endpoint == NOT_FOUND) {
handlerSpanName = "ResourceHttpRequestHandler.handleRequest";
}
span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL);
return span;
}
@Override
public String expectedHttpRoute(ServerEndpoint endpoint, String method) {
if (HttpConstants._OTHER.equals(method)) {
return getContextPath() + endpoint.getPath();
}
switch (endpoint.name()) {
case "PATH_PARAM":
return getContextPath() + "/path/{id}/param";
case "NOT_FOUND":
return getContextPath() + "/**";
default:
return super.expectedHttpRoute(endpoint, method);
}
}
@Override
protected List<Consumer<SpanDataAssert>> errorPageSpanAssertions(
String method, ServerEndpoint endpoint) {
List<Consumer<SpanDataAssert>> spanAssertions = new ArrayList<>();
spanAssertions.add(
span ->
span.hasName("BasicErrorController.error")
.hasKind(SpanKind.INTERNAL)
.hasAttributesSatisfying(Attributes::isEmpty));
return spanAssertions;
}
private static String getHandlerSpanName(ServerEndpoint endpoint) {
if (QUERY_PARAM.equals(endpoint)) {
return "TestController.queryParam";
} else if (PATH_PARAM.equals(endpoint)) {
return "TestController.pathParam";
} else if (CAPTURE_HEADERS.equals(endpoint)) {
return "TestController.captureHeaders";
} else if (INDEXED_CHILD.equals(endpoint)) {
return "TestController.indexedChild";
}
return "TestController." + endpoint.name().toLowerCase(Locale.ROOT);
}
}

View File

@ -0,0 +1,135 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.webmvc.filter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StreamUtils;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
public class FilteredAppConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.favorPathExtension(false)
.favorParameter(true)
.ignoreAcceptHeader(true)
.useJaf(false)
.defaultContentTypeStrategy(webRequest -> Collections.singletonList(MediaType.TEXT_PLAIN));
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
@Override
public void addFormatters(FormatterRegistry registry) {}
@Override
public void addInterceptors(InterceptorRegistry registry) {}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {}
@Override
public void addCorsMappings(CorsRegistry registry) {}
@Override
public void addViewControllers(ViewControllerRegistry registry) {}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
@Override
public void configureHandlerExceptionResolvers(
List<HandlerExceptionResolver> exceptionResolvers) {}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {}
@Override
public Validator getValidator() {
return null;
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
@Bean
HttpMessageConverter<Map<String, Object>> createPlainMapMessageConverter() {
return new AbstractHttpMessageConverter<Map<String, Object>>(MediaType.TEXT_PLAIN) {
@Override
protected boolean supports(Class<?> clazz) {
return Map.class.isAssignableFrom(clazz);
}
@Nullable
@Override
protected Map<String, Object> readInternal(
Class<? extends Map<String, Object>> clazz, HttpInputMessage inputMessage) {
return null;
}
@Override
protected void writeInternal(
Map<String, Object> stringObjectMap, HttpOutputMessage outputMessage) throws IOException {
StreamUtils.copy(
(String) stringObjectMap.get("message"),
StandardCharsets.UTF_8,
outputMessage.getBody());
}
};
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.webmvc.filter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.view.RedirectView;
/**
* None of the methods in this controller should be called because they are intercepted by the
* filter
*/
@Controller
public class TestController {
@RequestMapping("/success")
@ResponseBody
String success() throws Exception {
throw new Exception("This should not be called");
}
@RequestMapping("/query")
@ResponseBody
String queryParam(@RequestParam("some") String param) throws Exception {
throw new Exception("This should not be called");
}
@RequestMapping("/path/{id}/param")
@ResponseBody
String pathParam(@PathVariable Integer id) throws Exception {
throw new Exception("This should not be called");
}
@RequestMapping("/redirect")
@ResponseBody
RedirectView redirect() throws Exception {
throw new Exception("This should not be called");
}
@RequestMapping("/error-status")
ResponseEntity<Object> error() throws Exception {
throw new Exception("This should not be called");
}
@RequestMapping("/exception")
ResponseEntity<Object> exception() throws Exception {
throw new Exception("This should not be called");
}
@RequestMapping("/captureHeaders")
ResponseEntity<Object> captureHeaders() throws Exception {
throw new Exception("This should not be called");
}
@RequestMapping("/child")
@ResponseBody
ResponseEntity<Object> indexedChild(@RequestParam("id") String id) throws Exception {
throw new Exception("This should not be called");
}
@ExceptionHandler
ResponseEntity<String> handleException(Throwable throwable) {
return new ResponseEntity<>(throwable.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}

View File

@ -650,15 +650,15 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
span, endpoint == EXCEPTION ? options.expectedException : null);
span.hasParent(trace.getSpan(finalParentIndex));
});
if (options.hasRenderSpan.test(endpoint)) {
spanAssertions.add(span -> assertRenderSpan(span, method, endpoint));
}
}
if (options.hasResponseSpan.test(endpoint)) {
int parentIndex = spanAssertions.size() - 1;
spanAssertions.add(
span -> {
assertResponseSpan(span, method, endpoint);
span.hasParent(trace.getSpan(parentIndex));
});
span -> assertResponseSpan(span, trace.getSpan(parentIndex), method, endpoint));
}
if (options.hasErrorPageSpans.test(endpoint)) {
@ -699,12 +699,25 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
"assertHandlerSpan not implemented in " + getClass().getName());
}
@CanIgnoreReturnValue
protected SpanDataAssert assertResponseSpan(
SpanDataAssert span, SpanData parentSpan, String method, ServerEndpoint endpoint) {
span.hasParent(parentSpan);
return assertResponseSpan(span, method, endpoint);
}
protected SpanDataAssert assertResponseSpan(
SpanDataAssert span, String method, ServerEndpoint endpoint) {
throw new UnsupportedOperationException(
"assertResponseSpan not implemented in " + getClass().getName());
}
protected SpanDataAssert assertRenderSpan(
SpanDataAssert span, String method, ServerEndpoint endpoint) {
throw new UnsupportedOperationException(
"assertRenderSpan not implemented in " + getClass().getName());
}
protected List<Consumer<SpanDataAssert>> errorPageSpanAssertions(
String method, ServerEndpoint endpoint) {
throw new UnsupportedOperationException(

View File

@ -48,6 +48,7 @@ public final class HttpServerTestOptions {
Predicate<ServerEndpoint> hasHandlerSpan = unused -> false;
Predicate<ServerEndpoint> hasResponseSpan = unused -> false;
Predicate<ServerEndpoint> hasRenderSpan = unused -> false;
Predicate<ServerEndpoint> hasErrorPageSpans = unused -> false;
Predicate<ServerEndpoint> hasResponseCustomizer = unused -> false;
@ -132,6 +133,12 @@ public final class HttpServerTestOptions {
return this;
}
@CanIgnoreReturnValue
public HttpServerTestOptions setHasRenderSpan(Predicate<ServerEndpoint> hasRenderSpan) {
this.hasRenderSpan = hasRenderSpan;
return this;
}
@CanIgnoreReturnValue
public HttpServerTestOptions setHasErrorPageSpans(Predicate<ServerEndpoint> hasErrorPageSpans) {
this.hasErrorPageSpans = hasErrorPageSpans;