From 5fbc316cfca800e9cc0125a6b9e7986ac9b629c6 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Fri, 21 Aug 2020 19:17:49 +0200 Subject: [PATCH] Add missing RESTEasy (and Jersey) unit tests (#1058) * Add missing RESTEasy (and Jersey) unit tests * Standard HTTP server test (extending HttpServerTest) * AsyncResponse tests * Error handling in JAX-RS HTTP client * HTTP client JDK proxies (this one is RESTEasy only) --- .../src/test/groovy/JaxRsClientTest.groovy | 42 ++++ .../groovy/ResteasyProxyClientTest.groovy | 88 +++++++ .../jaxrs-2.0-resteasy-3.0.gradle | 6 +- .../jaxrs-2.0-resteasy-3.1.gradle | 10 +- .../jaxrs/jaxrs-2.0/jaxrs-2.0.gradle | 17 ++ .../test/groovy/JaxRsHttpServerTest.groovy | 231 ++++++++++++++++++ .../src/test/groovy/JaxRsTestResource.groovy | 129 ++++++++++ .../auto/test/base/HttpClientTest.groovy | 5 + .../auto/test/base/HttpServerTest.groovy | 4 + .../test/server/http/TestHttpServer.groovy | 6 + 10 files changed, 535 insertions(+), 3 deletions(-) create mode 100644 instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/ResteasyProxyClientTest.groovy create mode 100644 instrumentation/jaxrs/jaxrs-2.0/src/test/groovy/JaxRsHttpServerTest.groovy create mode 100644 instrumentation/jaxrs/jaxrs-2.0/src/test/groovy/JaxRsTestResource.groovy diff --git a/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientTest.groovy b/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientTest.groovy index ef456a1eb23..69a2a0b4d50 100644 --- a/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientTest.groovy +++ b/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/JaxRsClientTest.groovy @@ -14,7 +14,10 @@ * limitations under the License. */ +import static io.opentelemetry.trace.Span.Kind.CLIENT + import io.opentelemetry.auto.test.base.HttpClientTest +import io.opentelemetry.trace.attributes.SemanticAttributes import java.util.concurrent.TimeUnit import javax.ws.rs.client.Client import javax.ws.rs.client.ClientBuilder @@ -29,6 +32,7 @@ import org.glassfish.jersey.client.ClientProperties import org.glassfish.jersey.client.JerseyClientBuilder import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder import spock.lang.Timeout +import spock.lang.Unroll abstract class JaxRsClientTest extends HttpClientTest { @@ -47,6 +51,44 @@ abstract class JaxRsClientTest extends HttpClientTest { } abstract ClientBuilder builder() + + @Unroll + def "should properly convert HTTP status #statusCode to span error status"() { + given: + def method = "GET" + def uri = server.address.resolve(path) + + when: + def actualStatusCode = doRequest(method, uri) + + then: + assert actualStatusCode == statusCode + + assertTraces(1) { + trace(0, 2) { + span(0) { + parent() + operationName expectedOperationName(method) + spanKind CLIENT + errored true + attributes { + "${SemanticAttributes.NET_PEER_NAME.key()}" uri.host + "${SemanticAttributes.NET_PEER_IP.key()}" { it == null || it == "127.0.0.1" } + "${SemanticAttributes.NET_PEER_PORT.key()}" uri.port > 0 ? uri.port : { it == null || it == 443 } + "${SemanticAttributes.HTTP_URL.key()}" "${uri}" + "${SemanticAttributes.HTTP_METHOD.key()}" method + "${SemanticAttributes.HTTP_STATUS_CODE.key()}" statusCode + } + } + serverSpan(it, 1, span(0)) + } + } + + where: + path | statusCode + "/client-error" | 400 + "/error" | 500 + } } @Timeout(5) diff --git a/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/ResteasyProxyClientTest.groovy b/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/ResteasyProxyClientTest.groovy new file mode 100644 index 00000000000..2d90293a35a --- /dev/null +++ b/instrumentation/jaxrs-client/jaxrs-client-2.0/src/test/groovy/ResteasyProxyClientTest.groovy @@ -0,0 +1,88 @@ +/* + * Copyright The OpenTelemetry 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. + */ + +import io.opentelemetry.auto.test.base.HttpClientTest +import java.nio.charset.StandardCharsets +import javax.ws.rs.GET +import javax.ws.rs.HeaderParam +import javax.ws.rs.POST +import javax.ws.rs.PUT +import javax.ws.rs.Path +import javax.ws.rs.QueryParam +import javax.ws.rs.core.Response +import org.apache.http.client.utils.URLEncodedUtils +import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder +import org.jboss.resteasy.specimpl.ResteasyUriBuilder + +class ResteasyProxyClientTest extends HttpClientTest { + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + def proxyMethodName = "${method}_${uri.path}".toLowerCase() + .replace("/", "") + .replace('-', '_') + + def param = URLEncodedUtils.parse(uri, StandardCharsets.UTF_8.name()) + .stream().findFirst() + .map({ it.value }) + .orElse(null) + + def isTestServer = headers.get("is-test-server") + + def proxy = new ResteasyClientBuilder() + .build() + .target(new ResteasyUriBuilder().uri(server.address)) + .proxy(ResteasyProxyResource) + + def response = proxy."$proxyMethodName"(param, isTestServer) + + callback?.call() + + return response.status + } + + @Override + boolean testRedirects() { + false + } + + @Override + boolean testConnectionFailure() { + false + } + + @Override + boolean testRemoteConnection() { + false + } +} + +@Path("") +interface ResteasyProxyResource { + @GET + @Path("success") + Response get_success(@QueryParam("with") String param, + @HeaderParam("is-test-server") String isTestServer) + + @POST + @Path("success") + Response post_success(@QueryParam("with") String param, + @HeaderParam("is-test-server") String isTestServer) + + @PUT + @Path("success") + Response put_success(@QueryParam("with") String param, + @HeaderParam("is-test-server") String isTestServer) +} \ No newline at end of file diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/jaxrs-2.0-resteasy-3.0.gradle b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/jaxrs-2.0-resteasy-3.0.gradle index fc91802fc6d..33cbf29e51d 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/jaxrs-2.0-resteasy-3.0.gradle +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0/jaxrs-2.0-resteasy-3.0.gradle @@ -4,7 +4,9 @@ muzzle { // Cant assert fails because muzzle assumes all instrumentations will fail // Instrumentations in jaxrs-2.0 will pass - // Resteasy changes a class's package in 3.1.0 then moves it back in 3.5.0 + // Resteasy changes a class's package in 3.1.0 then moves it back in 3.5.0 and then moves it forward again in 4.0.0 + // so the jaxrs-2.0-resteasy-3.0 module applies to [3.0, 3.1) and [3.5, 4.0) + // and the jaxrs-2.0-resteasy-3.1 module applies to [3.1, 3.5) and [4.0, ) pass { group = "org.jboss.resteasy" module = "resteasy-jaxrs" @@ -14,7 +16,7 @@ muzzle { pass { group = "org.jboss.resteasy" module = "resteasy-jaxrs" - versions = "[3.5.0.Final,)" + versions = "[3.5.0.Final,4)" } } diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/jaxrs-2.0-resteasy-3.1.gradle b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/jaxrs-2.0-resteasy-3.1.gradle index 12440def5c7..c8b1c1210a1 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/jaxrs-2.0-resteasy-3.1.gradle +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1/jaxrs-2.0-resteasy-3.1.gradle @@ -4,12 +4,20 @@ muzzle { // Cant assert fails because muzzle assumes all instrumentations will fail // Instrumentations in jaxrs-2.0 will pass - // Resteasy changes a class's package in 3.1.0 then moves it back in 3.5.0 + // Resteasy changes a class's package in 3.1.0 then moves it back in 3.5.0 and then moves it forward again in 4.0.0 + // so the jaxrs-2.0-resteasy-3.0 module applies to [3.0, 3.1) and [3.5, 4.0) + // and the jaxrs-2.0-resteasy-3.1 module applies to [3.1, 3.5) and [4.0, ) pass { group = "org.jboss.resteasy" module = "resteasy-jaxrs" versions = "[3.1.0.Final,3.5.0.Final)" } + + pass { + group = "org.jboss.resteasy" + module = "resteasy-core" + versions = "[4.0.0.Final,)" + } } dependencies { diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0.gradle b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0.gradle index 645a24f5b99..b7453ef1c52 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0.gradle +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0.gradle @@ -38,8 +38,25 @@ dependencies { // Resteasy testLibrary group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.0.0.Final' + testImplementation(group: 'org.jboss.resteasy', name: 'resteasy-undertow', version: '3.0.4.Final') { + exclude group: 'org.jboss.resteasy', module: 'resteasy-client' + } + testImplementation group: 'io.undertow', name: 'undertow-servlet', version: '1.0.0.Final' resteasy31TestImplementation(group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: '3.1.0.Final') + resteasy31TestImplementation(group: 'org.jboss.resteasy', name: 'resteasy-undertow', version: '3.1.0.Final') { + exclude group: 'org.jboss.resteasy', module: 'resteasy-client' + } + + latestDepTestLibrary(group: 'org.jboss.resteasy', name: 'resteasy-undertow', version: '3.+') { + exclude group: 'org.jboss.resteasy', module: 'resteasy-client' + } + + // TODO: resteasy 4.+ has changed artifact name to resteasy-core +// latestDepTestLibrary group: 'org.jboss.resteasy', name: 'resteasy-core', version: '+' +// latestDepTestLibrary(group: 'org.jboss.resteasy', name: 'resteasy-undertow', version: '+') { +// exclude group: 'org.jboss.resteasy', module: 'resteasy-client' +// } } test.dependsOn resteasy31Test diff --git a/instrumentation/jaxrs/jaxrs-2.0/src/test/groovy/JaxRsHttpServerTest.groovy b/instrumentation/jaxrs/jaxrs-2.0/src/test/groovy/JaxRsHttpServerTest.groovy new file mode 100644 index 00000000000..74d650e2fcb --- /dev/null +++ b/instrumentation/jaxrs/jaxrs-2.0/src/test/groovy/JaxRsHttpServerTest.groovy @@ -0,0 +1,231 @@ +/* + * Copyright The OpenTelemetry 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. + */ + +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS +import static io.opentelemetry.trace.Span.Kind.INTERNAL +import static io.opentelemetry.trace.Span.Kind.SERVER + +import io.dropwizard.jetty.NonblockingServletHolder +import io.opentelemetry.auto.test.asserts.TraceAssert +import io.opentelemetry.auto.test.base.HttpServerTest +import io.opentelemetry.instrumentation.api.MoreAttributes +import io.opentelemetry.sdk.trace.data.SpanData +import io.opentelemetry.trace.attributes.SemanticAttributes +import io.undertow.Undertow +import okhttp3.HttpUrl +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.servlet.ServletContextHandler +import org.glassfish.jersey.server.ResourceConfig +import org.glassfish.jersey.servlet.ServletContainer +import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer +import spock.lang.Unroll + +abstract class JaxRsHttpServerTest extends HttpServerTest { + @Unroll + def "should handle #desc AsyncResponse"() { + given: + def url = HttpUrl.get(address.resolve("/async")).newBuilder() + .addQueryParameter("action", action) + .build() + def request = request(url, "GET", null).build() + + when: + def response = client.newCall(request).execute() + + then: + assert response.code() == statusCode + assert bodyPredicate(response.body().string()) + + assertTraces(1) { + trace(0, 2) { + asyncServerSpan(it, 0, url, statusCode) + handlerSpan(it, 1, span(0), "asyncOp", isCancelled, isError, errorMessage) + } + } + + where: + desc | action | statusCode | bodyPredicate | isCancelled | isError | errorMessage + "successful" | "succeed" | 200 | { it == "success" } | false | false | null + "failing" | "throw" | 500 | { it == "failure" } | false | true | "failure" + "canceled" | "cancel" | 503 | { it instanceof String } | true | false | null + } + + @Override + boolean hasHandlerSpan() { + true + } + + @Override + boolean testNotFound() { + false + } + + @Override + boolean testPathParam() { + true + } + + @Override + void serverSpan(TraceAssert trace, + int index, + String traceID = null, + String parentID = null, + String method = "GET", + Long responseContentLength = null, + ServerEndpoint endpoint = SUCCESS) { + serverSpan(trace, index, traceID, parentID, method, responseContentLength, + endpoint == PATH_PARAM ? "/path/{id}/param" : endpoint.resolvePath(address).path, + endpoint.resolve(address), + endpoint.errored, + endpoint.status, + endpoint.query) + } + + void asyncServerSpan(TraceAssert trace, + int index, + HttpUrl url, + int statusCode) { + def rawUrl = url.url() + serverSpan(trace, index, null, null, "GET", null, + rawUrl.path, + rawUrl.toURI(), + statusCode >= 500, + statusCode, + null) + } + + void serverSpan(TraceAssert trace, + int index, + String traceID, + String parentID, + String method, + Long responseContentLength, + String path, + URI fullUrl, + boolean isError, + int statusCode, + String query) { + trace.span(index) { + operationName method + " /" + path + spanKind SERVER + errored isError + if (parentID != null) { + traceId traceID + parentId parentID + } else { + parent() + } + attributes { + "${SemanticAttributes.NET_PEER_IP.key()}" { it == null || it == "127.0.0.1" } // Optional + "${SemanticAttributes.NET_PEER_PORT.key()}" Long + "${SemanticAttributes.HTTP_URL.key()}" fullUrl.toString() + "${SemanticAttributes.HTTP_METHOD.key()}" method + "${SemanticAttributes.HTTP_STATUS_CODE.key()}" statusCode + "${SemanticAttributes.HTTP_FLAVOR.key()}" "HTTP/1.1" + "${SemanticAttributes.HTTP_USER_AGENT.key()}" TEST_USER_AGENT + "${SemanticAttributes.HTTP_CLIENT_IP.key()}" TEST_CLIENT_IP + if (responseContentLength) { + "${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" responseContentLength + } else { + "${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH.key()}" Long + } + "servlet.path" String + "servlet.context" String + if (query) { + "$MoreAttributes.HTTP_QUERY" query + } + } + } + } + + @Override + void handlerSpan(TraceAssert trace, + int index, + Object parent, + String method = "GET", + ServerEndpoint endpoint = SUCCESS) { + handlerSpan(trace, index, parent, + endpoint.name().toLowerCase(), + false, + endpoint == EXCEPTION, + EXCEPTION.body) + } + + void handlerSpan(TraceAssert trace, + int index, + Object parent, + String methodName, + boolean isCancelled, + boolean isError, + String exceptionMessage = null) { + trace.span(index) { + operationName "JaxRsTestResource.${methodName}" + spanKind INTERNAL + errored isError + if (isError) { + errorEvent(Exception, exceptionMessage) + } + childOf((SpanData) parent) + attributes { + if (isCancelled) { + "canceled" true + } + } + } + } +} + +class ResteasyHttpServerTest extends JaxRsHttpServerTest { + + @Override + UndertowJaxrsServer startServer(int port) { + def server = new UndertowJaxrsServer() + server.deploy(JaxRsTestApplication) + server.start(Undertow.builder() + .addHttpListener(port, "localhost")) + return server + } + + @Override + void stopServer(UndertowJaxrsServer server) { + server.stop() + } +} + +class JerseyHttpServerTest extends JaxRsHttpServerTest { + + @Override + Server startServer(int port) { + def servlet = new ServletContainer(ResourceConfig.forApplicationClass(JaxRsTestApplication)) + + def handler = new ServletContextHandler(ServletContextHandler.SESSIONS) + handler.setContextPath("/") + handler.addServlet(new NonblockingServletHolder(servlet), "/*") + + def server = new Server(port) + server.setHandler(handler) + server.start() + + return server + } + + @Override + void stopServer(Server httpServer) { + httpServer.stop() + } +} \ No newline at end of file diff --git a/instrumentation/jaxrs/jaxrs-2.0/src/test/groovy/JaxRsTestResource.groovy b/instrumentation/jaxrs/jaxrs-2.0/src/test/groovy/JaxRsTestResource.groovy new file mode 100644 index 00000000000..1202f3ff7f9 --- /dev/null +++ b/instrumentation/jaxrs/jaxrs-2.0/src/test/groovy/JaxRsTestResource.groovy @@ -0,0 +1,129 @@ +/* + * Copyright The OpenTelemetry 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. + */ + +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.REDIRECT +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS + +import io.opentelemetry.auto.test.base.HttpServerTest +import java.util.concurrent.CompletableFuture +import javax.ws.rs.GET +import javax.ws.rs.Path +import javax.ws.rs.PathParam +import javax.ws.rs.QueryParam +import javax.ws.rs.container.AsyncResponse +import javax.ws.rs.container.Suspended +import javax.ws.rs.core.Application +import javax.ws.rs.core.Response +import javax.ws.rs.ext.ExceptionMapper + +@Path("") +class JaxRsTestResource { + @Path("/success") + @GET + String success() { + HttpServerTest.controller(SUCCESS) { + SUCCESS.body + } + } + + @Path("query") + @GET + String query_param(@QueryParam("some") String param) { + HttpServerTest.controller(QUERY_PARAM) { + "some=$param" + } + } + + @Path("redirect") + @GET + Response redirect() { + HttpServerTest.controller(REDIRECT) { + Response.status(Response.Status.FOUND) + .location(new URI(REDIRECT.body)) + .build() + } + } + + @Path("error-status") + @GET + Response error() { + HttpServerTest.controller(ERROR) { + Response.status(ERROR.status) + .entity(ERROR.body) + .build() + } + } + + @Path("exception") + @GET + Object exception() { + HttpServerTest.controller(EXCEPTION) { + throw new Exception(EXCEPTION.body) + } + } + + @Path("path/{id}/param") + @GET + String path_param(@PathParam("id") int id) { + HttpServerTest.controller(PATH_PARAM) { + id + } + } + + @Path("async") + @GET + void asyncOp(@Suspended AsyncResponse response, @QueryParam("action") String action) { + CompletableFuture.runAsync({ + switch (action) { + case "succeed": + response.resume("success") + break + case "throw": + response.resume(new Exception("failure")) + break + case "cancel": + response.cancel() + break + default: + response.resume(new AssertionError((Object) ("invalid action value: " + action))) + break + } + }) + } +} + +class JaxRsTestExceptionMapper implements ExceptionMapper { + @Override + Response toResponse(Exception exception) { + return Response.status(500) + .entity(exception.message) + .build() + } +} + +class JaxRsTestApplication extends Application { + @Override + Set> getClasses() { + def classes = new HashSet() + classes.add(JaxRsTestResource) + classes.add(JaxRsTestExceptionMapper) + return classes + } +} \ No newline at end of file diff --git a/testing-common/src/main/groovy/io/opentelemetry/auto/test/base/HttpClientTest.groovy b/testing-common/src/main/groovy/io/opentelemetry/auto/test/base/HttpClientTest.groovy index 954b7c7a23a..46fbee42d75 100644 --- a/testing-common/src/main/groovy/io/opentelemetry/auto/test/base/HttpClientTest.groovy +++ b/testing-common/src/main/groovy/io/opentelemetry/auto/test/base/HttpClientTest.groovy @@ -54,6 +54,11 @@ abstract class HttpClientTest extends AgentTestRunner { String msg = "Hello." response.status(200).send(msg) } + prefix("client-error") { + handleDistributedRequest() + String msg = "Invalid RQ" + response.status(400).send(msg) + } prefix("error") { handleDistributedRequest() String msg = "Sorry." diff --git a/testing-common/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy b/testing-common/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy index f2702c88668..7bacd026f1b 100644 --- a/testing-common/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy +++ b/testing-common/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy @@ -204,6 +204,10 @@ abstract class HttpServerTest extends AgentTestRunner { .query(uri.query) .fragment(uri.fragment) .build() + return request(url, method, body) + } + + Request.Builder request(HttpUrl url, String method, RequestBody body) { return new Request.Builder() .url(url) .method(method, body) diff --git a/testing-common/src/main/groovy/io/opentelemetry/auto/test/server/http/TestHttpServer.groovy b/testing-common/src/main/groovy/io/opentelemetry/auto/test/server/http/TestHttpServer.groovy index 3ac205754fc..90626a67e7b 100644 --- a/testing-common/src/main/groovy/io/opentelemetry/auto/test/server/http/TestHttpServer.groovy +++ b/testing-common/src/main/groovy/io/opentelemetry/auto/test/server/http/TestHttpServer.groovy @@ -324,6 +324,12 @@ class TestHttpServer implements AutoCloseable { resp.setContentLength(body.bytes.length) resp.writer.print(body) } + + void send(String body, String contentType) { + assert contentType != null + resp.setContentType(contentType) + send(body) + } } static class Headers {