From f951e323f65e211eae1330c614ba0038c4ed6712 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 5 Mar 2025 12:20:56 +0100 Subject: [PATCH 1/8] Also use port when checking if a request is made to Sentry DSN --- .../OpenTelemetryAttributesExtractor.java | 7 + .../OtelInternalSpanDetectionUtil.java | 18 +- .../OpenTelemetryAttributesExtractorTest.kt | 14 + .../OtelInternalSpanDetectionUtilTest.kt | 251 ++++++++++++++++++ sentry/src/main/java/io/sentry/DsnUtil.java | 9 +- sentry/src/test/java/io/sentry/DsnUtilTest.kt | 10 + 6 files changed, 295 insertions(+), 14 deletions(-) create mode 100644 sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelInternalSpanDetectionUtilTest.kt diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java index 87088ae2377..7d4db373df8 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java @@ -105,6 +105,7 @@ private static Map collectHeaders( return headers; } + @SuppressWarnings("deprecation") public @Nullable String extractUrl( final @NotNull Attributes attributes, final @NotNull SentryOptions options) { final @Nullable String urlFull = attributes.get(UrlAttributes.URL_FULL); @@ -112,6 +113,12 @@ private static Map collectHeaders( return urlFull; } + final @Nullable String deprecatedUrl = + attributes.get(io.opentelemetry.semconv.SemanticAttributes.HTTP_URL); + if (deprecatedUrl != null) { + return deprecatedUrl; + } + final String urlString = buildUrlString(attributes, options); if (!urlString.isEmpty()) { return urlString; diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelInternalSpanDetectionUtil.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelInternalSpanDetectionUtil.java index e4bd5a7e7c8..b1bbdede526 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelInternalSpanDetectionUtil.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelInternalSpanDetectionUtil.java @@ -2,7 +2,6 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.semconv.UrlAttributes; import io.sentry.DsnUtil; import io.sentry.IScopes; import java.util.Arrays; @@ -17,6 +16,8 @@ public final class OtelInternalSpanDetectionUtil { private static final @NotNull List spanKindsConsideredForSentryRequests = Arrays.asList(SpanKind.CLIENT, SpanKind.INTERNAL); + private static final @NotNull OpenTelemetryAttributesExtractor attributesExtractor = + new OpenTelemetryAttributesExtractor(); @SuppressWarnings("deprecation") public static boolean isSentryRequest( @@ -27,14 +28,8 @@ public static boolean isSentryRequest( return false; } - final @Nullable String httpUrl = - attributes.get(io.opentelemetry.semconv.SemanticAttributes.HTTP_URL); - if (DsnUtil.urlContainsDsnHost(scopes.getOptions(), httpUrl)) { - return true; - } - - final @Nullable String fullUrl = attributes.get(UrlAttributes.URL_FULL); - if (DsnUtil.urlContainsDsnHost(scopes.getOptions(), fullUrl)) { + String url = attributesExtractor.extractUrl(attributes, scopes.getOptions()); + if (DsnUtil.urlContainsDsnHost(scopes.getOptions(), url)) { return true; } @@ -43,10 +38,7 @@ public static boolean isSentryRequest( final @NotNull String spotlightUrl = optionsSpotlightUrl != null ? optionsSpotlightUrl : "http://localhost:8969/stream"; - if (containsSpotlightUrl(fullUrl, spotlightUrl)) { - return true; - } - if (containsSpotlightUrl(httpUrl, spotlightUrl)) { + if (containsSpotlightUrl(url, spotlightUrl)) { return true; } } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt index 1227509e0d0..e235ba8ca0b 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt @@ -4,6 +4,7 @@ import io.opentelemetry.api.common.AttributeKey import io.opentelemetry.sdk.internal.AttributesMap import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.semconv.HttpAttributes +import io.opentelemetry.semconv.SemanticAttributes import io.opentelemetry.semconv.ServerAttributes import io.opentelemetry.semconv.UrlAttributes import io.sentry.Scope @@ -202,6 +203,19 @@ class OpenTelemetryAttributesExtractorTest { assertEquals("https://sentry.io/some/path", url) } + @Test + fun `returns deprecated URL if present`() { + givenAttributes( + mapOf( + SemanticAttributes.HTTP_URL to "https://sentry.io/some/path" + ) + ) + + val url = whenExtractingUrl() + + assertEquals("https://sentry.io/some/path", url) + } + @Test fun `returns reconstructed URL if attributes present`() { givenAttributes( diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelInternalSpanDetectionUtilTest.kt b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelInternalSpanDetectionUtilTest.kt new file mode 100644 index 00000000000..6cc62dd1a0e --- /dev/null +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelInternalSpanDetectionUtilTest.kt @@ -0,0 +1,251 @@ +package io.sentry.opentelemetry + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.trace.SpanKind +import io.opentelemetry.sdk.internal.AttributesMap +import io.opentelemetry.semconv.HttpAttributes +import io.opentelemetry.semconv.SemanticAttributes +import io.opentelemetry.semconv.ServerAttributes +import io.opentelemetry.semconv.UrlAttributes +import io.sentry.IScopes +import io.sentry.SentryOptions +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class OtelInternalSpanDetectionUtilTest { + + private class Fixture { + val scopes = mock() + val attributes = AttributesMap.create(100, 100) + val options = SentryOptions.empty() + var spanKind: SpanKind = SpanKind.INTERNAL + + init { + whenever(scopes.options).thenReturn(options) + } + } + + private val fixture = Fixture() + + @Test + fun `detects split url as internal (span kind client)`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpanKind(SpanKind.CLIENT) + givenAttributes( + mapOf( + HttpAttributes.HTTP_REQUEST_METHOD to "GET", + UrlAttributes.URL_SCHEME to "https", + UrlAttributes.URL_PATH to "/path/to/123", + UrlAttributes.URL_QUERY to "q=123456&b=X", + ServerAttributes.SERVER_ADDRESS to "io.sentry", + ServerAttributes.SERVER_PORT to 8081L + ) + ) + + thenRequestIsConsideredInternal() + } + + @Test + fun `detects full url as internal (span kind client)`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpanKind(SpanKind.CLIENT) + givenAttributes( + mapOf( + UrlAttributes.URL_FULL to "https://io.sentry:8081" + ) + ) + + thenRequestIsConsideredInternal() + } + + @Test + fun `detects deprecated url as internal (span kind client)`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpanKind(SpanKind.CLIENT) + givenAttributes( + mapOf( + SemanticAttributes.HTTP_URL to "https://io.sentry:8081" + ) + ) + + thenRequestIsConsideredInternal() + } + + @Test + fun `detects split url as internal (span kind internal)`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpanKind(SpanKind.INTERNAL) + givenAttributes( + mapOf( + HttpAttributes.HTTP_REQUEST_METHOD to "GET", + UrlAttributes.URL_SCHEME to "https", + UrlAttributes.URL_PATH to "/path/to/123", + UrlAttributes.URL_QUERY to "q=123456&b=X", + ServerAttributes.SERVER_ADDRESS to "io.sentry", + ServerAttributes.SERVER_PORT to 8081L + ) + ) + + thenRequestIsConsideredInternal() + } + + @Test + fun `detects full url as internal (span kind internal)`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpanKind(SpanKind.INTERNAL) + givenAttributes( + mapOf( + UrlAttributes.URL_FULL to "https://io.sentry:8081" + ) + ) + + thenRequestIsConsideredInternal() + } + + @Test + fun `detects deprecated url as internal (span kind internal)`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpanKind(SpanKind.INTERNAL) + givenAttributes( + mapOf( + SemanticAttributes.HTTP_URL to "https://io.sentry:8081" + ) + ) + + thenRequestIsConsideredInternal() + } + + @Test + fun `does not detect full url as internal (span kind server)`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpanKind(SpanKind.SERVER) + givenAttributes( + mapOf( + UrlAttributes.URL_FULL to "https://io.sentry:8081" + ) + ) + + thenRequestIsNotConsideredInternal() + } + + @Test + fun `does not detect full url as internal (span kind producer)`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpanKind(SpanKind.PRODUCER) + givenAttributes( + mapOf( + UrlAttributes.URL_FULL to "https://io.sentry:8081" + ) + ) + + thenRequestIsNotConsideredInternal() + } + + @Test + fun `does not detect full url as internal (span kind consumer)`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpanKind(SpanKind.CONSUMER) + givenAttributes( + mapOf( + UrlAttributes.URL_FULL to "https://io.sentry:8081" + ) + ) + + thenRequestIsNotConsideredInternal() + } + + @Test + fun `detects full spotlight url as internal`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpotlightEnabled(true) + givenSpanKind(SpanKind.CLIENT) + givenAttributes( + mapOf( + UrlAttributes.URL_FULL to "http://localhost:8969/stream" + ) + ) + + thenRequestIsConsideredInternal() + } + + @Test + fun `detects full spotlight url as internal with custom spotlight url`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpotlightEnabled(true) + givenSpotlightUrl("http://localhost:8090/stream") + givenSpanKind(SpanKind.CLIENT) + givenAttributes( + mapOf( + UrlAttributes.URL_FULL to "http://localhost:8090/stream" + ) + ) + + thenRequestIsConsideredInternal() + } + + @Test + fun `does not detect mismatching full spotlight url as internal`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpotlightEnabled(true) + givenSpanKind(SpanKind.CLIENT) + givenAttributes( + mapOf( + UrlAttributes.URL_FULL to "http://localhost:8080/stream" + ) + ) + + thenRequestIsNotConsideredInternal() + } + + @Test + fun `does not detect mismatching full customized spotlight url as internal`() { + givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1") + givenSpotlightEnabled(true) + givenSpotlightUrl("http://localhost:8090/stream") + givenSpanKind(SpanKind.CLIENT) + givenAttributes( + mapOf( + UrlAttributes.URL_FULL to "http://localhost:8091/stream" + ) + ) + + thenRequestIsNotConsideredInternal() + } + + private fun givenAttributes(map: Map, Any>) { + map.forEach { k, v -> + fixture.attributes.put(k, v) + } + } + + private fun givenDsn(dsn: String) { + fixture.options.dsn = dsn + } + + private fun givenSpotlightEnabled(enabled: Boolean) { + fixture.options.isEnableSpotlight = enabled + } + + private fun givenSpotlightUrl(url: String) { + fixture.options.spotlightConnectionUrl = url + } + + private fun givenSpanKind(spanKind: SpanKind) { + fixture.spanKind = spanKind + } + + private fun thenRequestIsConsideredInternal() { + assertTrue(checkIfInternal()) + } + + private fun thenRequestIsNotConsideredInternal() { + assertFalse(checkIfInternal()) + } + + private fun checkIfInternal(): Boolean { + return OtelInternalSpanDetectionUtil.isSentryRequest(fixture.scopes, fixture.spanKind, fixture.attributes) + } +} diff --git a/sentry/src/main/java/io/sentry/DsnUtil.java b/sentry/src/main/java/io/sentry/DsnUtil.java index b6902ad2741..f31d1c286af 100644 --- a/sentry/src/main/java/io/sentry/DsnUtil.java +++ b/sentry/src/main/java/io/sentry/DsnUtil.java @@ -31,6 +31,13 @@ public static boolean urlContainsDsnHost(@Nullable SentryOptions options, @Nulla return false; } - return url.toLowerCase(Locale.ROOT).contains(dsnHost.toLowerCase(Locale.ROOT)); + final @NotNull String lowerCaseHost = dsnHost.toLowerCase(Locale.ROOT); + final int dsnPort = sentryUri.getPort(); + + if (dsnPort > 0) { + return url.toLowerCase(Locale.ROOT).contains(lowerCaseHost + ":" + dsnPort); + } else { + return url.toLowerCase(Locale.ROOT).contains(lowerCaseHost); + } } } diff --git a/sentry/src/test/java/io/sentry/DsnUtilTest.kt b/sentry/src/test/java/io/sentry/DsnUtilTest.kt index aa0f1c8e4b5..f231db98bb9 100644 --- a/sentry/src/test/java/io/sentry/DsnUtilTest.kt +++ b/sentry/src/test/java/io/sentry/DsnUtilTest.kt @@ -40,6 +40,16 @@ class DsnUtilTest { assertFalse(DsnUtil.urlContainsDsnHost(optionsWithDsn(DSN), null)) } + @Test + fun `returns false for same host but different port`() { + assertFalse(DsnUtil.urlContainsDsnHost(optionsWithDsn("http://publicKey:secretKey@localhost:8080/path/id?sample.rate=0.1"), "localhost:8081")) + } + + @Test + fun `returns true for same host and port`() { + assertTrue(DsnUtil.urlContainsDsnHost(optionsWithDsn("http://publicKey:secretKey@localhost:8080/path/id?sample.rate=0.1"), "localhost:8080")) + } + private fun optionsWithDsn(dsn: String?): SentryOptions { return SentryOptions().also { it.dsn = dsn From 47602d2d0c4c2527cbb7b5ccf4f3c91dca1ef7c4 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 5 Mar 2025 12:39:17 +0100 Subject: [PATCH 2/8] changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b7c2efd7a0..ee613b98583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +### Internal + +- Also use port when checking if a request is made to Sentry DSN ([#4231](https://github.com/getsentry/sentry-java/pull/4231)) + - For our OpenTelemetry integration we check if a span is for a request to Sentry + - We now also consider the port when performing this check + ## 8.3.0 ### Features From 282d7cdaf13414a06339d3310645e378a84983f5 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 5 Mar 2025 12:40:24 +0100 Subject: [PATCH 3/8] Add a param to control whether the test script should rebuild before running the tested server --- .github/workflows/system-tests-backend.yml | 2 +- test/system-test-run-all.sh | 18 +++++++++--------- test/system-test-run.sh | 6 ++++++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/system-tests-backend.yml b/.github/workflows/system-tests-backend.yml index 0656a5331ef..93d007b15b9 100644 --- a/.github/workflows/system-tests-backend.yml +++ b/.github/workflows/system-tests-backend.yml @@ -101,7 +101,7 @@ jobs: - name: Start server and run integration test for sentry-cli commands run: | - test/system-test-run.sh "${{ matrix.sample }}" "${{ matrix.agent }}" "${{ matrix.agent-auto-init }}" + test/system-test-run.sh "${{ matrix.sample }}" "${{ matrix.agent }}" "${{ matrix.agent-auto-init }}" "0" - name: Upload test results if: always() diff --git a/test/system-test-run-all.sh b/test/system-test-run-all.sh index e65a500b4d6..cc3fb523670 100755 --- a/test/system-test-run-all.sh +++ b/test/system-test-run-all.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash -./test/system-test-run.sh "sentry-samples-spring-boot" "0" "true" -./test/system-test-run.sh "sentry-samples-spring-boot-opentelemetry-noagent" "0" "true" -./test/system-test-run.sh "sentry-samples-spring-boot-opentelemetry" "1" "true" -./test/system-test-run.sh "sentry-samples-spring-boot-opentelemetry" "1" "false" -./test/system-test-run.sh "sentry-samples-spring-boot-webflux-jakarta" "0" "true" -./test/system-test-run.sh "sentry-samples-spring-boot-webflux" "0" "true" -./test/system-test-run.sh "sentry-samples-spring-boot-jakarta-opentelemetry-noagent" "0" "true" -./test/system-test-run.sh "sentry-samples-spring-boot-jakarta-opentelemetry" "1" "true" -./test/system-test-run.sh "sentry-samples-spring-boot-jakarta-opentelemetry" "1" "false" +./test/system-test-run.sh "sentry-samples-spring-boot" "0" "true" "0" +./test/system-test-run.sh "sentry-samples-spring-boot-opentelemetry-noagent" "0" "true" "0" +./test/system-test-run.sh "sentry-samples-spring-boot-opentelemetry" "1" "true" "0" +./test/system-test-run.sh "sentry-samples-spring-boot-opentelemetry" "1" "false" "0" +./test/system-test-run.sh "sentry-samples-spring-boot-webflux-jakarta" "0" "true" "0" +./test/system-test-run.sh "sentry-samples-spring-boot-webflux" "0" "true" "0" +./test/system-test-run.sh "sentry-samples-spring-boot-jakarta-opentelemetry-noagent" "0" "true" "0" +./test/system-test-run.sh "sentry-samples-spring-boot-jakarta-opentelemetry" "1" "true" "0" +./test/system-test-run.sh "sentry-samples-spring-boot-jakarta-opentelemetry" "1" "false" "0" diff --git a/test/system-test-run.sh b/test/system-test-run.sh index 7f1b47bed4f..9560beb3639 100755 --- a/test/system-test-run.sh +++ b/test/system-test-run.sh @@ -3,6 +3,12 @@ readonly SAMPLE_MODULE=$1 readonly JAVA_AGENT=$2 readonly JAVA_AGENT_AUTO_INIT=$3 +readonly BUILD_BEFORE_RUN=$4 + +if [[ "$BUILD_BEFORE_RUN" == "1" ]]; then + echo "Building before Test run" + ./gradlew :sentry-samples:${SAMPLE_MODULE}:assemble +fi test/system-test-sentry-server-start.sh MOCK_SERVER_PID=$(cat sentry-mock-server.pid) From 0b67b609bdaa4cbaf0e263605fd13d7f64cd3534 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 5 Mar 2025 12:42:32 +0100 Subject: [PATCH 4/8] Add system tests for distributed tracing --- .../jakarta/DistributedTracingController.java | 51 +++++ .../DistributedTracingSystemTest.kt | 182 ++++++++++++++++++ .../systemtest/GraphqlGreetingSystemTest.kt | 4 +- .../systemtest/GraphqlProjectSystemTest.kt | 6 +- .../systemtest/GraphqlTaskSystemTest.kt | 2 +- .../io/sentry/systemtest/PersonSystemTest.kt | 4 +- .../io/sentry/systemtest/TodoSystemTest.kt | 6 +- .../sentry/systemtest/util/RestTestClient.kt | 36 +++- .../io/sentry/systemtest/util/TestHelper.kt | 75 ++++++-- 9 files changed, 336 insertions(+), 30 deletions(-) create mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java create mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java new file mode 100644 index 00000000000..cfff0be4702 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java @@ -0,0 +1,51 @@ +package io.sentry.samples.spring.boot.jakarta; + +import io.opentelemetry.instrumentation.annotations.WithSpan; +import java.nio.charset.Charset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestClient; + +@RestController +@RequestMapping("/tracing/") +public class DistributedTracingController { + private static final Logger LOGGER = LoggerFactory.getLogger(DistributedTracingController.class); + private final RestClient restClient; + + public DistributedTracingController(RestClient restClient) { + this.restClient = restClient; + } + + @GetMapping("{id}") + @WithSpan("tracingSpanThroughOtelAnnotation") + Person person(@PathVariable Long id) { + return restClient + .get() + .uri("http://localhost:8080/person/{id}", id) + .header( + HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .retrieve() + .body(Person.class); + } + + @PostMapping + Person create(@RequestBody Person person) { + return restClient + .post() + .uri("http://localhost:8080/person/") + .body(person) + .header( + HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .retrieve() + .body(Person.class); + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt new file mode 100644 index 00000000000..3b4accef82a --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt @@ -0,0 +1,182 @@ +package io.sentry.systemtest + +import io.sentry.protocol.SentryId +import io.sentry.samples.spring.boot.jakarta.Person +import io.sentry.systemtest.util.TestHelper +import org.junit.Before +import org.springframework.http.HttpStatus +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class DistributedTracingSystemTest { + + lateinit var testHelper: TestHelper + + @Before + fun setup() { + testHelper = TestHelper("http://localhost:8080") + testHelper.reset() + } + + @Test + fun `get person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + "$traceId-424cffc8f94feeee-1", + "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } + + @Test + fun `get person distributed tracing with sampled false`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + "$traceId-424cffc8f94feeee-0", + "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=false,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" + } + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" + } + } + + @Test + fun `get person distributed tracing without sample_rand`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + "$traceId-424cffc8f94feeee-1", + "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + + var sampleRand1: String? = null + var sampleRand2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand1 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand2 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + assertEquals(sampleRand1, sampleRand2) + } + + @Test + fun `get person distributed tracing updates sample_rate on deferred decision`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + "$traceId-424cffc8f94feeee", + "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + + var sampleRate1: String? = null + var sampleRate2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate1 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate2 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + assertEquals(sampleRate1, sampleRate2) + assertNotEquals(sampleRate1, "0.5") + } + + @Test + fun `create person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + val person = Person("firstA", "lastB") + val returnedPerson = restClient.createPersonDistributedTracing( + person, + "$traceId-424cffc8f94feeee-1", + "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + + assertEquals(person.firstName, returnedPerson!!.firstName) + assertEquals(person.lastName, returnedPerson!!.lastName) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /tracing/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /person/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt index 769ae399bf0..5681c421a28 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt @@ -19,7 +19,7 @@ class GraphqlGreetingSystemTest { val response = testHelper.graphqlClient.greet("world") testHelper.ensureNoErrors(response) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } @@ -32,7 +32,7 @@ class GraphqlGreetingSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt index 74b196e33b6..bfa38fead33 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt @@ -23,7 +23,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertEquals("proj-slug", response?.data?.project?.slug) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.project") } } @@ -34,7 +34,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertNotNull(response?.data?.addProject) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Mutation.addProject") } } @@ -48,7 +48,7 @@ class GraphqlProjectSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Mutation.addProject") } } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt index 7a9283ac05a..7b8a1471542 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt @@ -30,7 +30,7 @@ class GraphqlTaskSystemTest { assertEquals("C3", firstTask.creatorId) assertEquals("C3", firstTask.creator?.id) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.tasks") } } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt index 8d83bde6309..3eed9c69cad 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt @@ -23,7 +23,7 @@ class PersonSystemTest { restClient.getPerson(1L) assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughSentryApi") } @@ -39,7 +39,7 @@ class PersonSystemTest { assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughSentryApi") } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt index 4c8ee45ea64..a32735e7e6b 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt @@ -22,7 +22,7 @@ class TodoSystemTest { restClient.getTodo(1L) assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "todoSpanOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "todoSpanSentryApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") @@ -35,7 +35,7 @@ class TodoSystemTest { restClient.getTodoWebclient(1L) assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") } } @@ -46,7 +46,7 @@ class TodoSystemTest { restClient.getTodoRestClient(1L) assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "todoRestClientSpanOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "todoRestClientSpanSentryApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt index 1b1d16c841f..f50632f381c 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt @@ -33,6 +33,36 @@ class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestCl } } + fun getPersonDistributedTracing(id: Long, sentryTraceHeader: String? = null, baggageHeader: String? = null): Person? { + return try { + val response = restTemplate().exchange("$backendBaseUrl/tracing/{id}", HttpMethod.GET, entityWithAuth(headerCallback = tracingHeaders(sentryTraceHeader, baggageHeader)), Person::class.java, mapOf("id" to id)) + lastKnownStatusCode = response.statusCode + response.body + } catch (e: HttpStatusCodeException) { + lastKnownStatusCode = e.statusCode + null + } + } + + fun createPersonDistributedTracing(person: Person, sentryTraceHeader: String? = null, baggageHeader: String? = null): Person? { + return try { + val response = restTemplate().exchange("$backendBaseUrl/tracing/", HttpMethod.POST, entityWithAuth(person, tracingHeaders(sentryTraceHeader, baggageHeader)), Person::class.java, person) + lastKnownStatusCode = response.statusCode + response.body + } catch (e: HttpStatusCodeException) { + lastKnownStatusCode = e.statusCode + null + } + } + + private fun tracingHeaders(sentryTraceHeader: String?, baggageHeader: String?): (HttpHeaders) -> HttpHeaders { + return { httpHeaders -> + sentryTraceHeader?.let { httpHeaders.set("sentry-trace", it) } + baggageHeader?.let { httpHeaders.set("baggage", it) } + httpHeaders + } + } + fun getTodo(id: Long): Todo? { return try { val response = restTemplate().exchange("$backendBaseUrl/todo/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) @@ -66,11 +96,13 @@ class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestCl } } - private fun entityWithAuth(request: Any? = null): HttpEntity { + private fun entityWithAuth(request: Any? = null, headerCallback: ((HttpHeaders) -> HttpHeaders)? = null): HttpEntity { val headers = HttpHeaders().also { it.setBasicAuth("user", "password") } - return HttpEntity(request, headers) + val modifiedHeaders = headerCallback?.invoke(headers) ?: headers + + return HttpEntity(request, modifiedHeaders) } } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt index 12960f4c528..14bac5cd0ea 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt @@ -3,6 +3,7 @@ package io.sentry.systemtest.util import com.apollographql.apollo3.api.ApolloResponse import com.apollographql.apollo3.api.Operation import io.sentry.JsonSerializer +import io.sentry.SentryEnvelopeHeader import io.sentry.SentryEvent import io.sentry.SentryItemType import io.sentry.SentryOptions @@ -54,27 +55,55 @@ class TestHelper(backendUrl: String) { throw RuntimeException("Unable to find matching envelope received by relay") } - fun ensureTransactionReceived(callback: ((SentryTransaction) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } + fun ensureNoEnvelopeReceived(callback: ((String) -> Boolean)) { + Thread.sleep(10000) + val envelopes = sentryClient.getEnvelopes() - val transactionItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Transaction } - if (transactionItem == null) { - return@ensureEnvelopeReceived false - } + if (envelopes.envelopes.isNullOrEmpty()) { + return + } - val transaction = transactionItem.getTransaction(jsonSerializer) - if (transaction == null) { - return@ensureEnvelopeReceived false + envelopes.envelopes.forEach { envelopeString -> + val didMatch = callback(envelopeString) + if (didMatch) { + throw RuntimeException("Found unexpected matching envelope received by relay") } + } + } + + fun ensureTransactionReceived(callback: ((SentryTransaction, SentryEnvelopeHeader) -> Boolean)) { + ensureEnvelopeReceived { envelopeString -> + checkIfTransactionMatches(envelopeString, callback) + } + } + + fun ensureNoTransactionReceived(callback: ((SentryTransaction, SentryEnvelopeHeader) -> Boolean)) { + ensureNoEnvelopeReceived { envelopeString -> + checkIfTransactionMatches(envelopeString, callback) + } + } + + private fun checkIfTransactionMatches(envelopeString: String, callback: ((SentryTransaction, SentryEnvelopeHeader) -> Boolean)): Boolean { + val deserializeEnvelope = + jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) + if (deserializeEnvelope == null) { + return false + } + + val envelopeHeader = deserializeEnvelope.header + + val transactionItem = + deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Transaction } + if (transactionItem == null) { + return false + } - callback(transaction) + val transaction = transactionItem.getTransaction(jsonSerializer) + if (transaction == null) { + return false } + + return callback(transaction, envelopeHeader) } fun ensureErrorReceived(callback: ((SentryEvent) -> Boolean)) { @@ -106,7 +135,7 @@ class TestHelper(backendUrl: String) { } fun ensureTransactionWithSpanReceived(callback: ((SentrySpan) -> Boolean)) { - ensureTransactionReceived { transaction -> + ensureTransactionReceived { transaction, envelopeHeader -> transaction.spans.forEach { span -> val callbackResult = callback(span) if (callbackResult) { @@ -126,6 +155,7 @@ class TestHelper(backendUrl: String) { PrintWriter(System.out).use { jsonSerializer.serialize(obj, it) } + println() } fun ensureNoErrors(response: ApolloResponse?) { @@ -159,4 +189,15 @@ class TestHelper(backendUrl: String) { return true } + + fun doesTransactionHaveTraceId(transaction: SentryTransaction, traceId: String): Boolean { + val spanContext = transaction.contexts.trace + if (spanContext?.traceId?.toString() != traceId) { + println("Unable to find trace ID $traceId in transaction:") + logObject(transaction) + return false + } + + return true + } } From 1675387be1d48c3f786a4777980e596f2f19e623 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 6 Mar 2025 10:20:05 +0100 Subject: [PATCH 5/8] reuse util classes for system tests --- build.gradle.kts | 2 +- buildSrc/src/main/java/Config.kt | 1 + codecov.yml | 1 + .../build.gradle.kts | 12 +- .../src/test/graphql/schema.graphqls | 70 --- .../DistributedTracingSystemTest.kt | 42 +- .../io/sentry/systemtest/PersonSystemTest.kt | 6 +- .../io/sentry/systemtest/TodoSystemTest.kt | 7 +- .../build.gradle.kts | 13 +- .../src/test/graphql/greeting.graphql | 3 - .../src/test/graphql/project.graphql | 11 - .../src/test/graphql/schema.graphqls | 70 --- .../src/test/graphql/task.graphql | 16 - .../systemtest/GraphqlGreetingSystemTest.kt | 4 +- .../systemtest/GraphqlProjectSystemTest.kt | 6 +- .../systemtest/GraphqlTaskSystemTest.kt | 2 +- .../io/sentry/systemtest/PersonSystemTest.kt | 10 +- .../io/sentry/systemtest/TodoSystemTest.kt | 13 +- .../systemtest/graphql/GraphqlTestClient.kt | 43 -- .../util/LoggingInsecureRestClient.kt | 13 - .../sentry/systemtest/util/RestTestClient.kt | 76 --- .../systemtest/util/SentryMockServerClient.kt | 43 -- .../io/sentry/systemtest/util/TestHelper.kt | 162 ----- .../build.gradle.kts | 13 +- .../src/test/graphql/greeting.graphql | 3 - .../src/test/graphql/project.graphql | 11 - .../src/test/graphql/schema.graphqls | 70 --- .../src/test/graphql/task.graphql | 16 - .../systemtest/GraphqlGreetingSystemTest.kt | 4 +- .../systemtest/GraphqlProjectSystemTest.kt | 6 +- .../systemtest/GraphqlTaskSystemTest.kt | 2 +- .../io/sentry/systemtest/PersonSystemTest.kt | 10 +- .../io/sentry/systemtest/TodoSystemTest.kt | 13 +- .../systemtest/graphql/GraphqlTestClient.kt | 43 -- .../util/LoggingInsecureRestClient.kt | 13 - .../sentry/systemtest/util/RestTestClient.kt | 76 --- .../systemtest/util/SentryMockServerClient.kt | 43 -- .../io/sentry/systemtest/util/TestHelper.kt | 173 ------ .../build.gradle.kts | 13 +- .../src/test/graphql/greeting.graphql | 3 - .../src/test/graphql/project.graphql | 11 - .../src/test/graphql/schema.graphqls | 70 --- .../src/test/graphql/task.graphql | 16 - .../systemtest/GraphqlGreetingSystemTest.kt | 4 +- .../systemtest/GraphqlProjectSystemTest.kt | 6 +- .../systemtest/GraphqlTaskSystemTest.kt | 2 +- .../io/sentry/systemtest/PersonSystemTest.kt | 10 +- .../io/sentry/systemtest/TodoSystemTest.kt | 9 +- .../systemtest/graphql/GraphqlTestClient.kt | 43 -- .../util/LoggingInsecureRestClient.kt | 32 - .../sentry/systemtest/util/RestTestClient.kt | 65 -- .../systemtest/util/SentryMockServerClient.kt | 43 -- .../io/sentry/systemtest/util/TestHelper.kt | 173 ------ .../build.gradle.kts | 13 +- .../src/test/graphql/greeting.graphql | 3 - .../src/test/graphql/project.graphql | 11 - .../src/test/graphql/schema.graphqls | 70 --- .../src/test/graphql/task.graphql | 16 - .../systemtest/GraphqlGreetingSystemTest.kt | 4 +- .../systemtest/GraphqlProjectSystemTest.kt | 6 +- .../systemtest/GraphqlTaskSystemTest.kt | 2 +- .../io/sentry/systemtest/PersonSystemTest.kt | 18 +- .../io/sentry/systemtest/TodoSystemTest.kt | 9 +- .../systemtest/graphql/GraphqlTestClient.kt | 43 -- .../util/LoggingInsecureRestClient.kt | 32 - .../sentry/systemtest/util/RestTestClient.kt | 68 --- .../systemtest/util/SentryMockServerClient.kt | 43 -- .../io/sentry/systemtest/util/TestHelper.kt | 173 ------ .../build.gradle.kts | 12 +- .../src/test/graphql/greeting.graphql | 3 - .../src/test/graphql/schema.graphqls | 3 - .../systemtest/GraphqlGreetingSystemTest.kt | 4 +- .../io/sentry/systemtest/PersonSystemTest.kt | 10 +- .../io/sentry/systemtest/TodoSystemTest.kt | 5 +- .../systemtest/graphql/GraphqlTestClient.kt | 28 - .../util/LoggingInsecureRestClient.kt | 13 - .../sentry/systemtest/util/RestTestClient.kt | 65 -- .../systemtest/util/SentryMockServerClient.kt | 43 -- .../io/sentry/systemtest/util/TestHelper.kt | 173 ------ .../build.gradle.kts | 13 +- .../src/test/graphql/greeting.graphql | 3 - .../src/test/graphql/schema.graphqls | 3 - .../systemtest/GraphqlGreetingSystemTest.kt | 4 +- .../io/sentry/systemtest/PersonSystemTest.kt | 10 +- .../io/sentry/systemtest/TodoSystemTest.kt | 5 +- .../systemtest/graphql/GraphqlTestClient.kt | 28 - .../util/LoggingInsecureRestClient.kt | 32 - .../sentry/systemtest/util/RestTestClient.kt | 65 -- .../systemtest/util/SentryMockServerClient.kt | 43 -- .../io/sentry/systemtest/util/TestHelper.kt | 173 ------ .../build.gradle.kts | 12 +- .../src/test/graphql/greeting.graphql | 3 - .../src/test/graphql/project.graphql | 11 - .../src/test/graphql/schema.graphqls | 70 --- .../src/test/graphql/task.graphql | 16 - .../systemtest/GraphqlGreetingSystemTest.kt | 4 +- .../systemtest/GraphqlProjectSystemTest.kt | 6 +- .../systemtest/GraphqlTaskSystemTest.kt | 2 +- .../io/sentry/systemtest/PersonSystemTest.kt | 10 +- .../io/sentry/systemtest/TodoSystemTest.kt | 9 +- .../systemtest/graphql/GraphqlTestClient.kt | 43 -- .../util/LoggingInsecureRestClient.kt | 32 - .../sentry/systemtest/util/RestTestClient.kt | 65 -- .../systemtest/util/SentryMockServerClient.kt | 43 -- .../io/sentry/systemtest/util/TestHelper.kt | 173 ------ .../api/sentry-system-test-support.api | 572 ++++++++++++++++++ sentry-system-test-support/build.gradle.kts | 54 ++ .../src/main}/graphql/greeting.graphql | 0 .../src/main}/graphql/project.graphql | 0 .../src/main}/graphql/task.graphql | 0 .../io/sentry/systemtest/ResponseTypes.kt | 10 + .../systemtest/graphql/GraphqlTestClient.kt | 0 .../util/LoggingInsecureRestClient.kt | 0 .../sentry/systemtest/util/RestTestClient.kt | 75 +-- .../systemtest/util/SentryMockServerClient.kt | 0 .../io/sentry/systemtest/util/TestHelper.kt | 11 + settings.gradle.kts | 1 + 117 files changed, 828 insertions(+), 3265 deletions(-) delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/graphql/schema.graphqls delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/greeting.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/project.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/schema.graphqls delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/task.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/greeting.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/project.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/schema.graphqls delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/task.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/greeting.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/project.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/schema.graphqls delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/task.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/greeting.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/project.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/schema.graphqls delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/task.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/graphql/greeting.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/graphql/schema.graphqls delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux/src/test/graphql/greeting.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux/src/test/graphql/schema.graphqls delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot/src/test/graphql/greeting.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot/src/test/graphql/project.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot/src/test/graphql/schema.graphqls delete mode 100644 sentry-samples/sentry-samples-spring-boot/src/test/graphql/task.graphql delete mode 100644 sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt delete mode 100644 sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt create mode 100644 sentry-system-test-support/api/sentry-system-test-support.api create mode 100644 sentry-system-test-support/build.gradle.kts rename {sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test => sentry-system-test-support/src/main}/graphql/greeting.graphql (100%) rename {sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test => sentry-system-test-support/src/main}/graphql/project.graphql (100%) rename {sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test => sentry-system-test-support/src/main}/graphql/task.graphql (100%) create mode 100644 sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/ResponseTypes.kt rename {sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test => sentry-system-test-support/src/main}/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt (100%) rename {sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test => sentry-system-test-support/src/main}/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt (100%) rename {sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test => sentry-system-test-support/src/main}/kotlin/io/sentry/systemtest/util/RestTestClient.kt (51%) rename {sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test => sentry-system-test-support/src/main}/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt (100%) rename {sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test => sentry-system-test-support/src/main}/kotlin/io/sentry/systemtest/util/TestHelper.kt (95%) diff --git a/build.gradle.kts b/build.gradle.kts index 1b2eb5614b9..38ff7b04393 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -155,7 +155,7 @@ subprojects { } } - if (!this.name.contains("sample") && !this.name.contains("integration-tests") && this.name != "sentry-test-support" && this.name != "sentry-compose-helper") { + if (!this.name.contains("sample") && !this.name.contains("integration-tests") && this.name != "sentry-system-test-support" && this.name != "sentry-test-support" && this.name != "sentry-compose-helper") { apply() apply() diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 0a3c62a1555..1fe657e61c6 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -75,6 +75,7 @@ object Config { val log4j2Core = "org.apache.logging.log4j:log4j-core:$log4j2Version" val jacksonDatabind = "com.fasterxml.jackson.core:jackson-databind" + val jacksonKotlin = "com.fasterxml.jackson.module:jackson-module-kotlin:2.18.3" val springBootStarter = "org.springframework.boot:spring-boot-starter:$springBootVersion" val springBootStarterGraphql = "org.springframework.boot:spring-boot-starter-graphql:$springBootVersion" diff --git a/codecov.yml b/codecov.yml index 7dd03ca5e85..66a0719b606 100644 --- a/codecov.yml +++ b/codecov.yml @@ -16,5 +16,6 @@ coverage: ignore: - "**/src/test/*" - "sentry-android-integration-tests/*" + - "sentry-system-test-support/*" - "sentry-test-support/*" - "sentry-samples/*" diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts index bd08b78c048..1f31fbfc05f 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts @@ -6,7 +6,6 @@ plugins { id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion kotlin("jvm") kotlin("plugin.spring") version Config.kotlinVersion - id("com.apollographql.apollo3") version "3.8.2" } group = "io.sentry.sample.spring-boot-jakarta" @@ -57,6 +56,7 @@ dependencies { // database query tracing implementation(projects.sentryJdbc) runtimeOnly(Config.TestLibs.hsqldb) + testImplementation(projects.sentrySystemTestSupport) testImplementation(Config.Libs.springBoot3StarterTest) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } @@ -103,13 +103,3 @@ tasks.named("test").configure { excludeTestsMatching("io.sentry.systemtest.*") } } - -apollo { - service("service") { - srcDir("src/test/graphql") - packageName.set("io.sentry.samples.graphql") - outputDirConnection { - connectToKotlinSourceSet("test") - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/graphql/schema.graphqls b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/graphql/schema.graphqls deleted file mode 100644 index d76aca4756a..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/graphql/schema.graphqls +++ /dev/null @@ -1,70 +0,0 @@ -type Query { - greeting(name: String! = "Spring"): String! - project(slug: ID!): Project - tasks(projectSlug: ID!): [Task] -} - -type Mutation { - addProject(slug: ID!): String! -} - -type Subscription { - notifyNewTask(projectSlug: ID!): Task -} - -""" A Project in the Spring portfolio """ -type Project { - """ Unique string id used in URLs """ - slug: ID! - """ Project name """ - name: String - """ URL of the git repository """ - repositoryUrl: String! - """ Current support status """ - status: ProjectStatus! -} - -""" A task """ -type Task { - """ ID """ - id: String! - """ Name """ - name: String! - """ ID of the Assignee """ - assigneeId: String - """ Assignee """ - assignee: Assignee - """ ID of the Creator """ - creatorId: String - """ Creator """ - creator: Creator -} - -""" An Assignee """ -type Assignee { - """ ID """ - id: String! - """ Name """ - name: String! -} - -""" An Creator """ -type Creator { - """ ID """ - id: String! - """ Name """ - name: String! -} - -enum ProjectStatus { - """ Actively supported by the Spring team """ - ACTIVE - """ Supported by the community """ - COMMUNITY - """ Prototype, not officially supported yet """ - INCUBATING - """ Project being retired, in maintenance mode """ - ATTIC - """ End-Of-Lifed """ - EOL -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt index 3b4accef82a..aa707f8b6e4 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt @@ -1,10 +1,8 @@ package io.sentry.systemtest import io.sentry.protocol.SentryId -import io.sentry.samples.spring.boot.jakarta.Person import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals @@ -25,10 +23,12 @@ class DistributedTracingSystemTest { val restClient = testHelper.restClient restClient.getPersonDistributedTracing( 1L, - "$traceId-424cffc8f94feeee-1", - "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) ) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) testHelper.ensureTransactionReceived { transaction, envelopeHeader -> transaction.transaction == "GET /tracing/{id}" && @@ -47,10 +47,12 @@ class DistributedTracingSystemTest { val restClient = testHelper.restClient restClient.getPersonDistributedTracing( 1L, - "$traceId-424cffc8f94feeee-0", - "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=false,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-0", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=false,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) ) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> transaction.transaction == "GET /tracing/{id}" @@ -67,10 +69,12 @@ class DistributedTracingSystemTest { val restClient = testHelper.restClient restClient.getPersonDistributedTracing( 1L, - "$traceId-424cffc8f94feeee-1", - "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) ) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) var sampleRand1: String? = null var sampleRand2: String? = null @@ -113,10 +117,12 @@ class DistributedTracingSystemTest { val restClient = testHelper.restClient restClient.getPersonDistributedTracing( 1L, - "$traceId-424cffc8f94feeee", - "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) ) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) var sampleRate1: String? = null var sampleRate2: String? = null @@ -161,10 +167,12 @@ class DistributedTracingSystemTest { val person = Person("firstA", "lastB") val returnedPerson = restClient.createPersonDistributedTracing( person, - "$traceId-424cffc8f94feeee-1", - "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) ) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt index 3eed9c69cad..4f7661dc4b3 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt @@ -1,9 +1,7 @@ package io.sentry.systemtest -import io.sentry.samples.spring.boot.jakarta.Person import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -21,7 +19,7 @@ class PersonSystemTest { fun `get person fails`() { val restClient = testHelper.restClient restClient.getPerson(1L) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") && @@ -34,7 +32,7 @@ class PersonSystemTest { val restClient = testHelper.restClient val person = Person("firstA", "lastB") val returnedPerson = restClient.createPerson(person) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt index a32735e7e6b..80b9bd4f310 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt @@ -2,7 +2,6 @@ package io.sentry.systemtest import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -20,7 +19,7 @@ class TodoSystemTest { fun `get todo works`() { val restClient = testHelper.restClient restClient.getTodo(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "todoSpanOtelApi") && @@ -33,7 +32,7 @@ class TodoSystemTest { fun `get todo webclient works`() { val restClient = testHelper.restClient restClient.getTodoWebclient(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") @@ -44,7 +43,7 @@ class TodoSystemTest { fun `get todo restclient works`() { val restClient = testHelper.restClient restClient.getTodoRestClient(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "todoRestClientSpanOtelApi") && diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts index acfc0d24d9e..dce546c8c4e 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts @@ -7,7 +7,6 @@ plugins { id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion kotlin("jvm") kotlin("plugin.spring") version Config.kotlinVersion - id("com.apollographql.apollo3") version "3.8.2" } group = "io.sentry.sample.spring-boot-jakarta" @@ -58,6 +57,8 @@ dependencies { // database query tracing implementation(projects.sentryJdbc) runtimeOnly(Config.TestLibs.hsqldb) + + testImplementation(projects.sentrySystemTestSupport) testImplementation(Config.Libs.springBoot3StarterTest) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } @@ -123,13 +124,3 @@ tasks.named("test").configure { excludeTestsMatching("io.sentry.systemtest.*") } } - -apollo { - service("service") { - srcDir("src/test/graphql") - packageName.set("io.sentry.samples.graphql") - outputDirConnection { - connectToKotlinSourceSet("test") - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/greeting.graphql b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/greeting.graphql deleted file mode 100644 index 06c866a65fa..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/greeting.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query GreetingQuery($name: String!) { - greeting(name: $name) -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/project.graphql b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/project.graphql deleted file mode 100644 index bff62ed2c2c..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/project.graphql +++ /dev/null @@ -1,11 +0,0 @@ -query ProjectQuery($slug: ID!) { - project(slug: $slug) { - slug - name - status - } -} - -mutation AddProjectMutation($slug: ID!) { - addProject(slug: $slug) -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/schema.graphqls b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/schema.graphqls deleted file mode 100644 index d76aca4756a..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/schema.graphqls +++ /dev/null @@ -1,70 +0,0 @@ -type Query { - greeting(name: String! = "Spring"): String! - project(slug: ID!): Project - tasks(projectSlug: ID!): [Task] -} - -type Mutation { - addProject(slug: ID!): String! -} - -type Subscription { - notifyNewTask(projectSlug: ID!): Task -} - -""" A Project in the Spring portfolio """ -type Project { - """ Unique string id used in URLs """ - slug: ID! - """ Project name """ - name: String - """ URL of the git repository """ - repositoryUrl: String! - """ Current support status """ - status: ProjectStatus! -} - -""" A task """ -type Task { - """ ID """ - id: String! - """ Name """ - name: String! - """ ID of the Assignee """ - assigneeId: String - """ Assignee """ - assignee: Assignee - """ ID of the Creator """ - creatorId: String - """ Creator """ - creator: Creator -} - -""" An Assignee """ -type Assignee { - """ ID """ - id: String! - """ Name """ - name: String! -} - -""" An Creator """ -type Creator { - """ ID """ - id: String! - """ Name """ - name: String! -} - -enum ProjectStatus { - """ Actively supported by the Spring team """ - ACTIVE - """ Supported by the community """ - COMMUNITY - """ Prototype, not officially supported yet """ - INCUBATING - """ Project being retired, in maintenance mode """ - ATTIC - """ End-Of-Lifed """ - EOL -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/task.graphql b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/task.graphql deleted file mode 100644 index 11ae18574d3..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/graphql/task.graphql +++ /dev/null @@ -1,16 +0,0 @@ -query TasksAndAssigneesQuery($slug: ID!) { - tasks(projectSlug: $slug) { - id - name - assigneeId - assignee { - id - name - } - creatorId - creator { - id - name - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt index b60f2b113a3..b4122d32311 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt @@ -19,7 +19,7 @@ class GraphqlGreetingSystemTest { val response = testHelper.graphqlClient.greet("world") testHelper.ensureNoErrors(response) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "query GreetingQuery") } } @@ -32,7 +32,7 @@ class GraphqlGreetingSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "query GreetingQuery") } } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt index 8946284be43..6452bdaf1ca 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt @@ -23,7 +23,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertEquals("proj-slug", response?.data?.project?.slug) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "query ProjectQuery") } } @@ -34,7 +34,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertNotNull(response?.data?.addProject) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "mutation AddProjectMutation") } } @@ -48,7 +48,7 @@ class GraphqlProjectSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "mutation AddProjectMutation") } } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt index 2fba967c824..38343e5b0e5 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt @@ -30,7 +30,7 @@ class GraphqlTaskSystemTest { assertEquals("C3", firstTask.creatorId) assertEquals("C3", firstTask.creator?.id) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "query TasksAndAssigneesQuery") } } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt index 8d83bde6309..4f7661dc4b3 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt @@ -1,9 +1,7 @@ package io.sentry.systemtest -import io.sentry.samples.spring.boot.jakarta.Person import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -21,9 +19,9 @@ class PersonSystemTest { fun `get person fails`() { val restClient = testHelper.restClient restClient.getPerson(1L) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughSentryApi") } @@ -34,12 +32,12 @@ class PersonSystemTest { val restClient = testHelper.restClient val person = Person("firstA", "lastB") val returnedPerson = restClient.createPerson(person) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughSentryApi") } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt index 4c8ee45ea64..80b9bd4f310 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt @@ -2,7 +2,6 @@ package io.sentry.systemtest import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -20,9 +19,9 @@ class TodoSystemTest { fun `get todo works`() { val restClient = testHelper.restClient restClient.getTodo(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "todoSpanOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "todoSpanSentryApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") @@ -33,9 +32,9 @@ class TodoSystemTest { fun `get todo webclient works`() { val restClient = testHelper.restClient restClient.getTodoWebclient(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") } } @@ -44,9 +43,9 @@ class TodoSystemTest { fun `get todo restclient works`() { val restClient = testHelper.restClient restClient.getTodoRestClient(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "todoRestClientSpanOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "todoRestClientSpanSentryApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt deleted file mode 100644 index 0c11906292b..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.graphql - -import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Mutation -import com.apollographql.apollo3.api.Query -import io.sentry.samples.graphql.AddProjectMutation -import io.sentry.samples.graphql.GreetingQuery -import io.sentry.samples.graphql.ProjectQuery -import io.sentry.samples.graphql.TasksAndAssigneesQuery -import kotlinx.coroutines.runBlocking - -class GraphqlTestClient(backendUrl: String) { - - val apollo = ApolloClient.Builder() - .serverUrl("$backendUrl/graphql") - .addHttpHeader("Authorization", "Basic dXNlcjpwYXNzd29yZA==") - .build() - - fun greet(name: String): ApolloResponse? { - return executeQuery(GreetingQuery(name)) - } - - fun project(slug: String): ApolloResponse? { - return executeQuery(ProjectQuery(slug)) - } - - fun tasksAndAssignees(slug: String): ApolloResponse? { - return executeQuery(TasksAndAssigneesQuery(slug)) - } - - fun addProject(slug: String): ApolloResponse? { - return executeMutation(AddProjectMutation(slug)) - } - - private fun executeQuery(query: Query): ApolloResponse? = runBlocking { - apollo.query(query).execute() - } - - private fun executeMutation(mutation: Mutation): ApolloResponse? = runBlocking { - apollo.mutation(mutation).execute() - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt deleted file mode 100644 index 17eea1a0084..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.sentry.systemtest.util - -import org.springframework.http.client.BufferingClientHttpRequestFactory -import org.springframework.web.client.RestTemplate - -open class LoggingInsecureRestClient { - - protected fun restTemplate(): RestTemplate { - return RestTemplate().also { - it.requestFactory = BufferingClientHttpRequestFactory(it.requestFactory) - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt deleted file mode 100644 index 1b1d16c841f..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt +++ /dev/null @@ -1,76 +0,0 @@ -package io.sentry.systemtest.util - -import io.sentry.samples.spring.boot.jakarta.Person -import io.sentry.samples.spring.boot.jakarta.Todo -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.HttpStatusCode -import org.springframework.web.client.HttpStatusCodeException - -class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestClient() { - var lastKnownStatusCode: HttpStatusCode? = null - - fun getPerson(id: Long): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/{id}", HttpMethod.GET, entityWithAuth(), Person::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun createPerson(person: Person): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/", HttpMethod.POST, entityWithAuth(person), Person::class.java, person) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodo(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodoWebclient(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo-webclient/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodoRestClient(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo-restclient/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders().also { - it.setBasicAuth("user", "password") - } - - return HttpEntity(request, headers) - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt deleted file mode 100644 index 7ef1699f122..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.util - -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod - -class SentryMockServerClient(private val baseUrl: String) : LoggingInsecureRestClient() { - - fun getEnvelopeCount(): EnvelopeCounts { - val response = restTemplate().exchange("$baseUrl/envelope-count", HttpMethod.GET, entityWithAuth(), EnvelopeCounts::class.java) - return response.body!! - } - - fun reset() { - restTemplate().exchange("$baseUrl/reset", HttpMethod.GET, entityWithAuth(), Any::class.java) - } - - fun getEnvelopes(): EnvelopesReceived { - val response = restTemplate().exchange("$baseUrl/envelopes-received", HttpMethod.GET, entityWithAuth(), EnvelopesReceived::class.java) - return response.body!! - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders() - return HttpEntity(request, headers) - } -} - -class EnvelopeCounts { - val envelopes: Long? = null - - override fun toString(): String { - return "EnvelopeCounts{envelopes=$envelopes}" - } -} - -class EnvelopesReceived { - val envelopes: List? = null - - override fun toString(): String { - return "EnvelopesReceived{envelopes=$envelopes}" - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt deleted file mode 100644 index 12960f4c528..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt +++ /dev/null @@ -1,162 +0,0 @@ -package io.sentry.systemtest.util - -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Operation -import io.sentry.JsonSerializer -import io.sentry.SentryEvent -import io.sentry.SentryItemType -import io.sentry.SentryOptions -import io.sentry.protocol.SentrySpan -import io.sentry.protocol.SentryTransaction -import io.sentry.systemtest.graphql.GraphqlTestClient -import java.io.PrintWriter -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class TestHelper(backendUrl: String) { - - val restClient: RestTestClient - val graphqlClient: GraphqlTestClient - val sentryClient: SentryMockServerClient - val jsonSerializer: JsonSerializer - - var envelopeCounts: EnvelopeCounts? = null - - init { - restClient = RestTestClient(backendUrl) - sentryClient = SentryMockServerClient("http://localhost:8000") - graphqlClient = GraphqlTestClient(backendUrl) - jsonSerializer = JsonSerializer(SentryOptions.empty()) - } - - fun snapshotEnvelopeCount() { - envelopeCounts = sentryClient.getEnvelopeCount() - } - - fun ensureEnvelopeCountIncreased() { - Thread.sleep(1000) - val envelopeCountsAfter = sentryClient.getEnvelopeCount() - assertTrue(envelopeCountsAfter!!.envelopes!! > envelopeCounts!!.envelopes!!) - } - - fun ensureEnvelopeReceived(callback: ((String) -> Boolean)) { - Thread.sleep(10000) - val envelopes = sentryClient.getEnvelopes() - assertNotNull(envelopes.envelopes) - envelopes.envelopes.forEach { envelopeString -> - val didMatch = callback(envelopeString) - if (didMatch) { - return - } - } - throw RuntimeException("Unable to find matching envelope received by relay") - } - - fun ensureTransactionReceived(callback: ((SentryTransaction) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val transactionItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Transaction } - if (transactionItem == null) { - return@ensureEnvelopeReceived false - } - - val transaction = transactionItem.getTransaction(jsonSerializer) - if (transaction == null) { - return@ensureEnvelopeReceived false - } - - callback(transaction) - } - } - - fun ensureErrorReceived(callback: ((SentryEvent) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val errorItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Event } - if (errorItem == null) { - return@ensureEnvelopeReceived false - } - - val error = errorItem.getEvent(jsonSerializer) - if (error == null) { - return@ensureEnvelopeReceived false - } - - val callbackResult = callback(error) - if (!callbackResult) { - println("found an error event but it did not match:") - logObject(error) - } - callbackResult - } - } - - fun ensureTransactionWithSpanReceived(callback: ((SentrySpan) -> Boolean)) { - ensureTransactionReceived { transaction -> - transaction.spans.forEach { span -> - val callbackResult = callback(span) - if (callbackResult) { - return@ensureTransactionReceived true - } - } - false - } - } - - fun reset() { - sentryClient.reset() - } - - fun logObject(obj: Any?) { - obj ?: return - PrintWriter(System.out).use { - jsonSerializer.serialize(obj, it) - } - } - - fun ensureNoErrors(response: ApolloResponse?) { - response ?: throw RuntimeException("no response") - assertFalse(response.hasErrors()) - } - - fun ensureErrorCount(response: ApolloResponse?, errorCount: Int) { - response ?: throw RuntimeException("no response") - assertEquals(errorCount, response.errors?.size) - } - - fun doesTransactionContainSpanWithOp(transaction: SentryTransaction, op: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.op == op } - if (span == null) { - println("Unable to find span with op $op in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionContainSpanWithDescription(transaction: SentryTransaction, description: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.description == description } - if (span == null) { - println("Unable to find span with description $description in transaction:") - logObject(transaction) - return false - } - - return true - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts index 242859656c3..11e6b613466 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts @@ -6,7 +6,6 @@ plugins { id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion kotlin("jvm") kotlin("plugin.spring") version Config.kotlinVersion - id("com.apollographql.apollo3") version "3.8.2" } group = "io.sentry.sample.spring-boot-jakarta" @@ -56,6 +55,8 @@ dependencies { // database query tracing implementation(projects.sentryJdbc) runtimeOnly(Config.TestLibs.hsqldb) + + testImplementation(projects.sentrySystemTestSupport) testImplementation(Config.Libs.springBoot3StarterTest) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } @@ -97,13 +98,3 @@ tasks.named("test").configure { excludeTestsMatching("io.sentry.systemtest.*") } } - -apollo { - service("service") { - srcDir("src/test/graphql") - packageName.set("io.sentry.samples.graphql") - outputDirConnection { - connectToKotlinSourceSet("test") - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/greeting.graphql b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/greeting.graphql deleted file mode 100644 index 06c866a65fa..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/greeting.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query GreetingQuery($name: String!) { - greeting(name: $name) -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/project.graphql b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/project.graphql deleted file mode 100644 index bff62ed2c2c..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/project.graphql +++ /dev/null @@ -1,11 +0,0 @@ -query ProjectQuery($slug: ID!) { - project(slug: $slug) { - slug - name - status - } -} - -mutation AddProjectMutation($slug: ID!) { - addProject(slug: $slug) -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/schema.graphqls b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/schema.graphqls deleted file mode 100644 index d76aca4756a..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/schema.graphqls +++ /dev/null @@ -1,70 +0,0 @@ -type Query { - greeting(name: String! = "Spring"): String! - project(slug: ID!): Project - tasks(projectSlug: ID!): [Task] -} - -type Mutation { - addProject(slug: ID!): String! -} - -type Subscription { - notifyNewTask(projectSlug: ID!): Task -} - -""" A Project in the Spring portfolio """ -type Project { - """ Unique string id used in URLs """ - slug: ID! - """ Project name """ - name: String - """ URL of the git repository """ - repositoryUrl: String! - """ Current support status """ - status: ProjectStatus! -} - -""" A task """ -type Task { - """ ID """ - id: String! - """ Name """ - name: String! - """ ID of the Assignee """ - assigneeId: String - """ Assignee """ - assignee: Assignee - """ ID of the Creator """ - creatorId: String - """ Creator """ - creator: Creator -} - -""" An Assignee """ -type Assignee { - """ ID """ - id: String! - """ Name """ - name: String! -} - -""" An Creator """ -type Creator { - """ ID """ - id: String! - """ Name """ - name: String! -} - -enum ProjectStatus { - """ Actively supported by the Spring team """ - ACTIVE - """ Supported by the community """ - COMMUNITY - """ Prototype, not officially supported yet """ - INCUBATING - """ Project being retired, in maintenance mode """ - ATTIC - """ End-Of-Lifed """ - EOL -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/task.graphql b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/task.graphql deleted file mode 100644 index 11ae18574d3..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/graphql/task.graphql +++ /dev/null @@ -1,16 +0,0 @@ -query TasksAndAssigneesQuery($slug: ID!) { - tasks(projectSlug: $slug) { - id - name - assigneeId - assignee { - id - name - } - creatorId - creator { - id - name - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt index 769ae399bf0..5681c421a28 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt @@ -19,7 +19,7 @@ class GraphqlGreetingSystemTest { val response = testHelper.graphqlClient.greet("world") testHelper.ensureNoErrors(response) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } @@ -32,7 +32,7 @@ class GraphqlGreetingSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt index 74b196e33b6..bfa38fead33 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt @@ -23,7 +23,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertEquals("proj-slug", response?.data?.project?.slug) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.project") } } @@ -34,7 +34,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertNotNull(response?.data?.addProject) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Mutation.addProject") } } @@ -48,7 +48,7 @@ class GraphqlProjectSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Mutation.addProject") } } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt index cf2aebfd090..0f634f309bd 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt @@ -30,7 +30,7 @@ class GraphqlTaskSystemTest { assertEquals("C3", firstTask.creatorId) assertEquals("C3", firstTask.creator?.id) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.tasks") && testHelper.doesTransactionContainSpanWithDescription(transaction, "Task.assignee") && testHelper.doesTransactionContainSpanWithDescription(transaction, "Task.creator") diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt index e4a388ef529..c190b86d6b0 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt @@ -1,9 +1,7 @@ package io.sentry.systemtest -import io.sentry.samples.spring.boot.jakarta.Person import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -21,9 +19,9 @@ class PersonSystemTest { fun `get person fails`() { val restClient = testHelper.restClient restClient.getPerson(1L) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionHaveOp(transaction, "http.server") } } @@ -33,12 +31,12 @@ class PersonSystemTest { val restClient = testHelper.restClient val person = Person("firstA", "lastB") val returnedPerson = restClient.createPerson(person) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "PersonService.create") && testHelper.doesTransactionContainSpanWithOp(transaction, "db.query") } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt index 9893bcb3966..3ef84872a81 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt @@ -2,7 +2,6 @@ package io.sentry.systemtest import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -20,9 +19,9 @@ class TodoSystemTest { fun `get todo works`() { val restClient = testHelper.restClient restClient.getTodo(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") } } @@ -31,9 +30,9 @@ class TodoSystemTest { fun `get todo webclient works`() { val restClient = testHelper.restClient restClient.getTodoWebclient(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") } } @@ -42,9 +41,9 @@ class TodoSystemTest { fun `get todo restclient works`() { val restClient = testHelper.restClient restClient.getTodoRestClient(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") } } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt deleted file mode 100644 index 0c11906292b..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.graphql - -import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Mutation -import com.apollographql.apollo3.api.Query -import io.sentry.samples.graphql.AddProjectMutation -import io.sentry.samples.graphql.GreetingQuery -import io.sentry.samples.graphql.ProjectQuery -import io.sentry.samples.graphql.TasksAndAssigneesQuery -import kotlinx.coroutines.runBlocking - -class GraphqlTestClient(backendUrl: String) { - - val apollo = ApolloClient.Builder() - .serverUrl("$backendUrl/graphql") - .addHttpHeader("Authorization", "Basic dXNlcjpwYXNzd29yZA==") - .build() - - fun greet(name: String): ApolloResponse? { - return executeQuery(GreetingQuery(name)) - } - - fun project(slug: String): ApolloResponse? { - return executeQuery(ProjectQuery(slug)) - } - - fun tasksAndAssignees(slug: String): ApolloResponse? { - return executeQuery(TasksAndAssigneesQuery(slug)) - } - - fun addProject(slug: String): ApolloResponse? { - return executeMutation(AddProjectMutation(slug)) - } - - private fun executeQuery(query: Query): ApolloResponse? = runBlocking { - apollo.query(query).execute() - } - - private fun executeMutation(mutation: Mutation): ApolloResponse? = runBlocking { - apollo.mutation(mutation).execute() - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt deleted file mode 100644 index 17eea1a0084..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.sentry.systemtest.util - -import org.springframework.http.client.BufferingClientHttpRequestFactory -import org.springframework.web.client.RestTemplate - -open class LoggingInsecureRestClient { - - protected fun restTemplate(): RestTemplate { - return RestTemplate().also { - it.requestFactory = BufferingClientHttpRequestFactory(it.requestFactory) - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt deleted file mode 100644 index 1b1d16c841f..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt +++ /dev/null @@ -1,76 +0,0 @@ -package io.sentry.systemtest.util - -import io.sentry.samples.spring.boot.jakarta.Person -import io.sentry.samples.spring.boot.jakarta.Todo -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.HttpStatusCode -import org.springframework.web.client.HttpStatusCodeException - -class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestClient() { - var lastKnownStatusCode: HttpStatusCode? = null - - fun getPerson(id: Long): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/{id}", HttpMethod.GET, entityWithAuth(), Person::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun createPerson(person: Person): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/", HttpMethod.POST, entityWithAuth(person), Person::class.java, person) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodo(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodoWebclient(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo-webclient/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodoRestClient(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo-restclient/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders().also { - it.setBasicAuth("user", "password") - } - - return HttpEntity(request, headers) - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt deleted file mode 100644 index 7ef1699f122..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.util - -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod - -class SentryMockServerClient(private val baseUrl: String) : LoggingInsecureRestClient() { - - fun getEnvelopeCount(): EnvelopeCounts { - val response = restTemplate().exchange("$baseUrl/envelope-count", HttpMethod.GET, entityWithAuth(), EnvelopeCounts::class.java) - return response.body!! - } - - fun reset() { - restTemplate().exchange("$baseUrl/reset", HttpMethod.GET, entityWithAuth(), Any::class.java) - } - - fun getEnvelopes(): EnvelopesReceived { - val response = restTemplate().exchange("$baseUrl/envelopes-received", HttpMethod.GET, entityWithAuth(), EnvelopesReceived::class.java) - return response.body!! - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders() - return HttpEntity(request, headers) - } -} - -class EnvelopeCounts { - val envelopes: Long? = null - - override fun toString(): String { - return "EnvelopeCounts{envelopes=$envelopes}" - } -} - -class EnvelopesReceived { - val envelopes: List? = null - - override fun toString(): String { - return "EnvelopesReceived{envelopes=$envelopes}" - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt deleted file mode 100644 index 1017d847348..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt +++ /dev/null @@ -1,173 +0,0 @@ -package io.sentry.systemtest.util - -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Operation -import io.sentry.JsonSerializer -import io.sentry.SentryEvent -import io.sentry.SentryItemType -import io.sentry.SentryOptions -import io.sentry.protocol.SentrySpan -import io.sentry.protocol.SentryTransaction -import io.sentry.systemtest.graphql.GraphqlTestClient -import java.io.PrintWriter -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class TestHelper(backendUrl: String) { - - val restClient: RestTestClient - val graphqlClient: GraphqlTestClient - val sentryClient: SentryMockServerClient - val jsonSerializer: JsonSerializer - - var envelopeCounts: EnvelopeCounts? = null - - init { - restClient = RestTestClient(backendUrl) - sentryClient = SentryMockServerClient("http://localhost:8000") - graphqlClient = GraphqlTestClient(backendUrl) - jsonSerializer = JsonSerializer(SentryOptions.empty()) - } - - fun snapshotEnvelopeCount() { - envelopeCounts = sentryClient.getEnvelopeCount() - } - - fun ensureEnvelopeCountIncreased() { - Thread.sleep(1000) - val envelopeCountsAfter = sentryClient.getEnvelopeCount() - assertTrue(envelopeCountsAfter!!.envelopes!! > envelopeCounts!!.envelopes!!) - } - - fun ensureEnvelopeReceived(callback: ((String) -> Boolean)) { - Thread.sleep(10000) - val envelopes = sentryClient.getEnvelopes() - assertNotNull(envelopes.envelopes) - envelopes.envelopes.forEach { envelopeString -> - val didMatch = callback(envelopeString) - if (didMatch) { - return - } - } - throw RuntimeException("Unable to find matching envelope received by relay") - } - - fun ensureTransactionReceived(callback: ((SentryTransaction) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val transactionItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Transaction } - if (transactionItem == null) { - return@ensureEnvelopeReceived false - } - - val transaction = transactionItem.getTransaction(jsonSerializer) - if (transaction == null) { - return@ensureEnvelopeReceived false - } - - callback(transaction) - } - } - - fun ensureErrorReceived(callback: ((SentryEvent) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val errorItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Event } - if (errorItem == null) { - return@ensureEnvelopeReceived false - } - - val error = errorItem.getEvent(jsonSerializer) - if (error == null) { - return@ensureEnvelopeReceived false - } - - val callbackResult = callback(error) - if (!callbackResult) { - println("found an error event but it did not match:") - logObject(error) - } - callbackResult - } - } - - fun ensureTransactionWithSpanReceived(callback: ((SentrySpan) -> Boolean)) { - ensureTransactionReceived { transaction -> - transaction.spans.forEach { span -> - val callbackResult = callback(span) - if (callbackResult) { - return@ensureTransactionReceived true - } - } - false - } - } - - fun reset() { - sentryClient.reset() - } - - fun logObject(obj: Any?) { - obj ?: return - PrintWriter(System.out).use { - jsonSerializer.serialize(obj, it) - } - } - - fun ensureNoErrors(response: ApolloResponse?) { - response ?: throw RuntimeException("no response") - assertFalse(response.hasErrors()) - } - - fun ensureErrorCount(response: ApolloResponse?, errorCount: Int) { - response ?: throw RuntimeException("no response") - assertEquals(errorCount, response.errors?.size) - } - - fun doesTransactionContainSpanWithOp(transaction: SentryTransaction, op: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.op == op } - if (span == null) { - println("Unable to find span with op $op in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionContainSpanWithDescription(transaction: SentryTransaction, description: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.description == description } - if (span == null) { - println("Unable to find span with description $description in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionHaveOp(transaction: SentryTransaction, op: String): Boolean { - val matches = transaction.contexts.trace?.operation == op - if (!matches) { - println("Unable to find transaction with op $op:") - logObject(transaction) - return false - } - - return true - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index c6dd41951cc..fa30b6d3b16 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -6,7 +6,6 @@ plugins { id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion kotlin("jvm") kotlin("plugin.spring") version Config.kotlinVersion - id("com.apollographql.apollo3") version "3.8.2" } group = "io.sentry.sample.spring-boot" @@ -57,6 +56,8 @@ dependencies { // database query tracing implementation(projects.sentryJdbc) runtimeOnly(Config.TestLibs.hsqldb) + + testImplementation(projects.sentrySystemTestSupport) testImplementation(Config.Libs.springBootStarterTest) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } @@ -104,13 +105,3 @@ tasks.named("test").configure { excludeTestsMatching("io.sentry.systemtest.*") } } - -apollo { - service("service") { - srcDir("src/test/graphql") - packageName.set("io.sentry.samples.graphql") - outputDirConnection { - connectToKotlinSourceSet("test") - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/greeting.graphql b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/greeting.graphql deleted file mode 100644 index 06c866a65fa..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/greeting.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query GreetingQuery($name: String!) { - greeting(name: $name) -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/project.graphql b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/project.graphql deleted file mode 100644 index bff62ed2c2c..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/project.graphql +++ /dev/null @@ -1,11 +0,0 @@ -query ProjectQuery($slug: ID!) { - project(slug: $slug) { - slug - name - status - } -} - -mutation AddProjectMutation($slug: ID!) { - addProject(slug: $slug) -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/schema.graphqls b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/schema.graphqls deleted file mode 100644 index d76aca4756a..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/schema.graphqls +++ /dev/null @@ -1,70 +0,0 @@ -type Query { - greeting(name: String! = "Spring"): String! - project(slug: ID!): Project - tasks(projectSlug: ID!): [Task] -} - -type Mutation { - addProject(slug: ID!): String! -} - -type Subscription { - notifyNewTask(projectSlug: ID!): Task -} - -""" A Project in the Spring portfolio """ -type Project { - """ Unique string id used in URLs """ - slug: ID! - """ Project name """ - name: String - """ URL of the git repository """ - repositoryUrl: String! - """ Current support status """ - status: ProjectStatus! -} - -""" A task """ -type Task { - """ ID """ - id: String! - """ Name """ - name: String! - """ ID of the Assignee """ - assigneeId: String - """ Assignee """ - assignee: Assignee - """ ID of the Creator """ - creatorId: String - """ Creator """ - creator: Creator -} - -""" An Assignee """ -type Assignee { - """ ID """ - id: String! - """ Name """ - name: String! -} - -""" An Creator """ -type Creator { - """ ID """ - id: String! - """ Name """ - name: String! -} - -enum ProjectStatus { - """ Actively supported by the Spring team """ - ACTIVE - """ Supported by the community """ - COMMUNITY - """ Prototype, not officially supported yet """ - INCUBATING - """ Project being retired, in maintenance mode """ - ATTIC - """ End-Of-Lifed """ - EOL -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/task.graphql b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/task.graphql deleted file mode 100644 index 11ae18574d3..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/graphql/task.graphql +++ /dev/null @@ -1,16 +0,0 @@ -query TasksAndAssigneesQuery($slug: ID!) { - tasks(projectSlug: $slug) { - id - name - assigneeId - assignee { - id - name - } - creatorId - creator { - id - name - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt index 769ae399bf0..5681c421a28 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt @@ -19,7 +19,7 @@ class GraphqlGreetingSystemTest { val response = testHelper.graphqlClient.greet("world") testHelper.ensureNoErrors(response) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } @@ -32,7 +32,7 @@ class GraphqlGreetingSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt index 74b196e33b6..bfa38fead33 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt @@ -23,7 +23,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertEquals("proj-slug", response?.data?.project?.slug) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.project") } } @@ -34,7 +34,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertNotNull(response?.data?.addProject) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Mutation.addProject") } } @@ -48,7 +48,7 @@ class GraphqlProjectSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Mutation.addProject") } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt index 7a9283ac05a..7b8a1471542 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt @@ -30,7 +30,7 @@ class GraphqlTaskSystemTest { assertEquals("C3", firstTask.creatorId) assertEquals("C3", firstTask.creator?.id) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.tasks") } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt index 9309529ce44..4f7661dc4b3 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt @@ -1,9 +1,7 @@ package io.sentry.systemtest -import io.sentry.samples.spring.boot.Person import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -21,9 +19,9 @@ class PersonSystemTest { fun `get person fails`() { val restClient = testHelper.restClient restClient.getPerson(1L) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughSentryApi") } @@ -34,12 +32,12 @@ class PersonSystemTest { val restClient = testHelper.restClient val person = Person("firstA", "lastB") val returnedPerson = restClient.createPerson(person) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughSentryApi") } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt index a48ea15fdd5..8b472ede78e 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt @@ -2,7 +2,6 @@ package io.sentry.systemtest import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -20,9 +19,9 @@ class TodoSystemTest { fun `get todo works`() { val restClient = testHelper.restClient restClient.getTodo(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "todoSpanOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "todoSpanSentryApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") @@ -33,9 +32,9 @@ class TodoSystemTest { fun `get todo webclient works`() { val restClient = testHelper.restClient restClient.getTodoWebclient(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt deleted file mode 100644 index 0c11906292b..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.graphql - -import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Mutation -import com.apollographql.apollo3.api.Query -import io.sentry.samples.graphql.AddProjectMutation -import io.sentry.samples.graphql.GreetingQuery -import io.sentry.samples.graphql.ProjectQuery -import io.sentry.samples.graphql.TasksAndAssigneesQuery -import kotlinx.coroutines.runBlocking - -class GraphqlTestClient(backendUrl: String) { - - val apollo = ApolloClient.Builder() - .serverUrl("$backendUrl/graphql") - .addHttpHeader("Authorization", "Basic dXNlcjpwYXNzd29yZA==") - .build() - - fun greet(name: String): ApolloResponse? { - return executeQuery(GreetingQuery(name)) - } - - fun project(slug: String): ApolloResponse? { - return executeQuery(ProjectQuery(slug)) - } - - fun tasksAndAssignees(slug: String): ApolloResponse? { - return executeQuery(TasksAndAssigneesQuery(slug)) - } - - fun addProject(slug: String): ApolloResponse? { - return executeMutation(AddProjectMutation(slug)) - } - - private fun executeQuery(query: Query): ApolloResponse? = runBlocking { - apollo.query(query).execute() - } - - private fun executeMutation(mutation: Mutation): ApolloResponse? = runBlocking { - apollo.mutation(mutation).execute() - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt deleted file mode 100644 index 0577f0eef21..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt +++ /dev/null @@ -1,32 +0,0 @@ -package io.sentry.systemtest.util - -import org.apache.http.impl.client.HttpClients -import org.springframework.http.client.BufferingClientHttpRequestFactory -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory -import org.springframework.web.client.RestTemplate - -open class LoggingInsecureRestClient { - - protected fun restTemplate(): RestTemplate { - val requestFactory = BufferingClientHttpRequestFactory( - HttpComponentsClientHttpRequestFactory(HttpClients.createDefault()) - ) - return RestTemplate(requestFactory).also { - it.messageConverters.add(0, jacksonConverter()) - } - } - - private fun jacksonConverter(): org.springframework.http.converter.json.MappingJackson2HttpMessageConverter { - val converter = org.springframework.http.converter.json.MappingJackson2HttpMessageConverter() - converter.objectMapper = objectMapper() - return converter - } - - private fun objectMapper(): com.fasterxml.jackson.databind.ObjectMapper { - val builder = org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.json() - val objectMapper: com.fasterxml.jackson.databind.ObjectMapper = builder.createXmlMapper(false).build() - objectMapper.registerModule(com.fasterxml.jackson.datatype.jsr310.JavaTimeModule()) - objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - return objectMapper - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt deleted file mode 100644 index f5d5bd7ee38..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt +++ /dev/null @@ -1,65 +0,0 @@ -package io.sentry.systemtest.util - -import io.sentry.samples.spring.boot.Person -import io.sentry.samples.spring.boot.Todo -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.HttpStatus -import org.springframework.web.client.HttpStatusCodeException - -class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestClient() { - var lastKnownStatusCode: HttpStatus? = null - - fun getPerson(id: Long): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/{id}", HttpMethod.GET, entityWithAuth(), Person::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun createPerson(person: Person): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/", HttpMethod.POST, entityWithAuth(person), Person::class.java, person) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodo(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodoWebclient(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo-webclient/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders().also { - it.setBasicAuth("user", "password") - } - - return HttpEntity(request, headers) - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt deleted file mode 100644 index 7ef1699f122..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.util - -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod - -class SentryMockServerClient(private val baseUrl: String) : LoggingInsecureRestClient() { - - fun getEnvelopeCount(): EnvelopeCounts { - val response = restTemplate().exchange("$baseUrl/envelope-count", HttpMethod.GET, entityWithAuth(), EnvelopeCounts::class.java) - return response.body!! - } - - fun reset() { - restTemplate().exchange("$baseUrl/reset", HttpMethod.GET, entityWithAuth(), Any::class.java) - } - - fun getEnvelopes(): EnvelopesReceived { - val response = restTemplate().exchange("$baseUrl/envelopes-received", HttpMethod.GET, entityWithAuth(), EnvelopesReceived::class.java) - return response.body!! - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders() - return HttpEntity(request, headers) - } -} - -class EnvelopeCounts { - val envelopes: Long? = null - - override fun toString(): String { - return "EnvelopeCounts{envelopes=$envelopes}" - } -} - -class EnvelopesReceived { - val envelopes: List? = null - - override fun toString(): String { - return "EnvelopesReceived{envelopes=$envelopes}" - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt deleted file mode 100644 index 1017d847348..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt +++ /dev/null @@ -1,173 +0,0 @@ -package io.sentry.systemtest.util - -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Operation -import io.sentry.JsonSerializer -import io.sentry.SentryEvent -import io.sentry.SentryItemType -import io.sentry.SentryOptions -import io.sentry.protocol.SentrySpan -import io.sentry.protocol.SentryTransaction -import io.sentry.systemtest.graphql.GraphqlTestClient -import java.io.PrintWriter -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class TestHelper(backendUrl: String) { - - val restClient: RestTestClient - val graphqlClient: GraphqlTestClient - val sentryClient: SentryMockServerClient - val jsonSerializer: JsonSerializer - - var envelopeCounts: EnvelopeCounts? = null - - init { - restClient = RestTestClient(backendUrl) - sentryClient = SentryMockServerClient("http://localhost:8000") - graphqlClient = GraphqlTestClient(backendUrl) - jsonSerializer = JsonSerializer(SentryOptions.empty()) - } - - fun snapshotEnvelopeCount() { - envelopeCounts = sentryClient.getEnvelopeCount() - } - - fun ensureEnvelopeCountIncreased() { - Thread.sleep(1000) - val envelopeCountsAfter = sentryClient.getEnvelopeCount() - assertTrue(envelopeCountsAfter!!.envelopes!! > envelopeCounts!!.envelopes!!) - } - - fun ensureEnvelopeReceived(callback: ((String) -> Boolean)) { - Thread.sleep(10000) - val envelopes = sentryClient.getEnvelopes() - assertNotNull(envelopes.envelopes) - envelopes.envelopes.forEach { envelopeString -> - val didMatch = callback(envelopeString) - if (didMatch) { - return - } - } - throw RuntimeException("Unable to find matching envelope received by relay") - } - - fun ensureTransactionReceived(callback: ((SentryTransaction) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val transactionItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Transaction } - if (transactionItem == null) { - return@ensureEnvelopeReceived false - } - - val transaction = transactionItem.getTransaction(jsonSerializer) - if (transaction == null) { - return@ensureEnvelopeReceived false - } - - callback(transaction) - } - } - - fun ensureErrorReceived(callback: ((SentryEvent) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val errorItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Event } - if (errorItem == null) { - return@ensureEnvelopeReceived false - } - - val error = errorItem.getEvent(jsonSerializer) - if (error == null) { - return@ensureEnvelopeReceived false - } - - val callbackResult = callback(error) - if (!callbackResult) { - println("found an error event but it did not match:") - logObject(error) - } - callbackResult - } - } - - fun ensureTransactionWithSpanReceived(callback: ((SentrySpan) -> Boolean)) { - ensureTransactionReceived { transaction -> - transaction.spans.forEach { span -> - val callbackResult = callback(span) - if (callbackResult) { - return@ensureTransactionReceived true - } - } - false - } - } - - fun reset() { - sentryClient.reset() - } - - fun logObject(obj: Any?) { - obj ?: return - PrintWriter(System.out).use { - jsonSerializer.serialize(obj, it) - } - } - - fun ensureNoErrors(response: ApolloResponse?) { - response ?: throw RuntimeException("no response") - assertFalse(response.hasErrors()) - } - - fun ensureErrorCount(response: ApolloResponse?, errorCount: Int) { - response ?: throw RuntimeException("no response") - assertEquals(errorCount, response.errors?.size) - } - - fun doesTransactionContainSpanWithOp(transaction: SentryTransaction, op: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.op == op } - if (span == null) { - println("Unable to find span with op $op in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionContainSpanWithDescription(transaction: SentryTransaction, description: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.description == description } - if (span == null) { - println("Unable to find span with description $description in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionHaveOp(transaction: SentryTransaction, op: String): Boolean { - val matches = transaction.contexts.trace?.operation == op - if (!matches) { - println("Unable to find transaction with op $op:") - logObject(transaction) - return false - } - - return true - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index be159d82445..5bf0d001881 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -7,7 +7,6 @@ plugins { id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion kotlin("jvm") kotlin("plugin.spring") version Config.kotlinVersion - id("com.apollographql.apollo3") version "3.8.2" } group = "io.sentry.sample.spring-boot" @@ -58,6 +57,8 @@ dependencies { // database query tracing implementation(projects.sentryJdbc) runtimeOnly(Config.TestLibs.hsqldb) + + testImplementation(projects.sentrySystemTestSupport) testImplementation(Config.Libs.springBootStarterTest) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } @@ -124,13 +125,3 @@ tasks.named("test").configure { excludeTestsMatching("io.sentry.systemtest.*") } } - -apollo { - service("service") { - srcDir("src/test/graphql") - packageName.set("io.sentry.samples.graphql") - outputDirConnection { - connectToKotlinSourceSet("test") - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/greeting.graphql b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/greeting.graphql deleted file mode 100644 index 06c866a65fa..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/greeting.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query GreetingQuery($name: String!) { - greeting(name: $name) -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/project.graphql b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/project.graphql deleted file mode 100644 index bff62ed2c2c..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/project.graphql +++ /dev/null @@ -1,11 +0,0 @@ -query ProjectQuery($slug: ID!) { - project(slug: $slug) { - slug - name - status - } -} - -mutation AddProjectMutation($slug: ID!) { - addProject(slug: $slug) -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/schema.graphqls b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/schema.graphqls deleted file mode 100644 index d76aca4756a..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/schema.graphqls +++ /dev/null @@ -1,70 +0,0 @@ -type Query { - greeting(name: String! = "Spring"): String! - project(slug: ID!): Project - tasks(projectSlug: ID!): [Task] -} - -type Mutation { - addProject(slug: ID!): String! -} - -type Subscription { - notifyNewTask(projectSlug: ID!): Task -} - -""" A Project in the Spring portfolio """ -type Project { - """ Unique string id used in URLs """ - slug: ID! - """ Project name """ - name: String - """ URL of the git repository """ - repositoryUrl: String! - """ Current support status """ - status: ProjectStatus! -} - -""" A task """ -type Task { - """ ID """ - id: String! - """ Name """ - name: String! - """ ID of the Assignee """ - assigneeId: String - """ Assignee """ - assignee: Assignee - """ ID of the Creator """ - creatorId: String - """ Creator """ - creator: Creator -} - -""" An Assignee """ -type Assignee { - """ ID """ - id: String! - """ Name """ - name: String! -} - -""" An Creator """ -type Creator { - """ ID """ - id: String! - """ Name """ - name: String! -} - -enum ProjectStatus { - """ Actively supported by the Spring team """ - ACTIVE - """ Supported by the community """ - COMMUNITY - """ Prototype, not officially supported yet """ - INCUBATING - """ Project being retired, in maintenance mode """ - ATTIC - """ End-Of-Lifed """ - EOL -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/task.graphql b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/task.graphql deleted file mode 100644 index 11ae18574d3..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/graphql/task.graphql +++ /dev/null @@ -1,16 +0,0 @@ -query TasksAndAssigneesQuery($slug: ID!) { - tasks(projectSlug: $slug) { - id - name - assigneeId - assignee { - id - name - } - creatorId - creator { - id - name - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt index b60f2b113a3..b4122d32311 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt @@ -19,7 +19,7 @@ class GraphqlGreetingSystemTest { val response = testHelper.graphqlClient.greet("world") testHelper.ensureNoErrors(response) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "query GreetingQuery") } } @@ -32,7 +32,7 @@ class GraphqlGreetingSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "query GreetingQuery") } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt index 8946284be43..6452bdaf1ca 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt @@ -23,7 +23,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertEquals("proj-slug", response?.data?.project?.slug) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "query ProjectQuery") } } @@ -34,7 +34,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertNotNull(response?.data?.addProject) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "mutation AddProjectMutation") } } @@ -48,7 +48,7 @@ class GraphqlProjectSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "mutation AddProjectMutation") } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt index 2fba967c824..38343e5b0e5 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt @@ -30,7 +30,7 @@ class GraphqlTaskSystemTest { assertEquals("C3", firstTask.creatorId) assertEquals("C3", firstTask.creator?.id) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "query TasksAndAssigneesQuery") } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt index cfa459213c3..b0807f3fbc1 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt @@ -1,9 +1,7 @@ package io.sentry.systemtest -import io.sentry.samples.spring.boot.Person import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -21,9 +19,9 @@ class PersonSystemTest { fun `get person fails`() { val restClient = testHelper.restClient restClient.getPerson(1L) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughSentryApi") } @@ -34,12 +32,12 @@ class PersonSystemTest { val restClient = testHelper.restClient val person = Person("firstA", "lastB") val returnedPerson = restClient.createPerson(person) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughSentryApi") } @@ -56,12 +54,12 @@ class PersonSystemTest { "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=f9118105af4a2d42b4124532cd1065ff,sentry-transaction=HTTP%20GET" ) ) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughSentryApi") } @@ -78,12 +76,12 @@ class PersonSystemTest { "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=f9118105af4a2d42b4124532cd1065ff,sentry-transaction=HTTP%20GET" ) ) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughSentryApi") } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt index a48ea15fdd5..8b472ede78e 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt @@ -2,7 +2,6 @@ package io.sentry.systemtest import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -20,9 +19,9 @@ class TodoSystemTest { fun `get todo works`() { val restClient = testHelper.restClient restClient.getTodo(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "todoSpanOtelApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "todoSpanSentryApi") && testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") @@ -33,9 +32,9 @@ class TodoSystemTest { fun `get todo webclient works`() { val restClient = testHelper.restClient restClient.getTodoWebclient(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt deleted file mode 100644 index 0c11906292b..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.graphql - -import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Mutation -import com.apollographql.apollo3.api.Query -import io.sentry.samples.graphql.AddProjectMutation -import io.sentry.samples.graphql.GreetingQuery -import io.sentry.samples.graphql.ProjectQuery -import io.sentry.samples.graphql.TasksAndAssigneesQuery -import kotlinx.coroutines.runBlocking - -class GraphqlTestClient(backendUrl: String) { - - val apollo = ApolloClient.Builder() - .serverUrl("$backendUrl/graphql") - .addHttpHeader("Authorization", "Basic dXNlcjpwYXNzd29yZA==") - .build() - - fun greet(name: String): ApolloResponse? { - return executeQuery(GreetingQuery(name)) - } - - fun project(slug: String): ApolloResponse? { - return executeQuery(ProjectQuery(slug)) - } - - fun tasksAndAssignees(slug: String): ApolloResponse? { - return executeQuery(TasksAndAssigneesQuery(slug)) - } - - fun addProject(slug: String): ApolloResponse? { - return executeMutation(AddProjectMutation(slug)) - } - - private fun executeQuery(query: Query): ApolloResponse? = runBlocking { - apollo.query(query).execute() - } - - private fun executeMutation(mutation: Mutation): ApolloResponse? = runBlocking { - apollo.mutation(mutation).execute() - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt deleted file mode 100644 index 0577f0eef21..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt +++ /dev/null @@ -1,32 +0,0 @@ -package io.sentry.systemtest.util - -import org.apache.http.impl.client.HttpClients -import org.springframework.http.client.BufferingClientHttpRequestFactory -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory -import org.springframework.web.client.RestTemplate - -open class LoggingInsecureRestClient { - - protected fun restTemplate(): RestTemplate { - val requestFactory = BufferingClientHttpRequestFactory( - HttpComponentsClientHttpRequestFactory(HttpClients.createDefault()) - ) - return RestTemplate(requestFactory).also { - it.messageConverters.add(0, jacksonConverter()) - } - } - - private fun jacksonConverter(): org.springframework.http.converter.json.MappingJackson2HttpMessageConverter { - val converter = org.springframework.http.converter.json.MappingJackson2HttpMessageConverter() - converter.objectMapper = objectMapper() - return converter - } - - private fun objectMapper(): com.fasterxml.jackson.databind.ObjectMapper { - val builder = org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.json() - val objectMapper: com.fasterxml.jackson.databind.ObjectMapper = builder.createXmlMapper(false).build() - objectMapper.registerModule(com.fasterxml.jackson.datatype.jsr310.JavaTimeModule()) - objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - return objectMapper - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt deleted file mode 100644 index 1c4eef63f78..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt +++ /dev/null @@ -1,68 +0,0 @@ -package io.sentry.systemtest.util - -import io.sentry.samples.spring.boot.Person -import io.sentry.samples.spring.boot.Todo -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.HttpStatus -import org.springframework.web.client.HttpStatusCodeException - -class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestClient() { - var lastKnownStatusCode: HttpStatus? = null - - fun getPerson(id: Long): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/{id}", HttpMethod.GET, entityWithAuth(), Person::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun createPerson(person: Person, extraHeaders: Map? = null): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/", HttpMethod.POST, entityWithAuth(person, extraHeaders), Person::class.java, person) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodo(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodoWebclient(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo-webclient/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - private fun entityWithAuth(request: Any? = null, extraHeaders: Map? = null): HttpEntity { - val headers = HttpHeaders().also { httpHeaders -> - httpHeaders.setBasicAuth("user", "password") - extraHeaders?.forEach { key, value -> - httpHeaders.set(key, value) - } - } - - return HttpEntity(request, headers) - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt deleted file mode 100644 index 7ef1699f122..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.util - -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod - -class SentryMockServerClient(private val baseUrl: String) : LoggingInsecureRestClient() { - - fun getEnvelopeCount(): EnvelopeCounts { - val response = restTemplate().exchange("$baseUrl/envelope-count", HttpMethod.GET, entityWithAuth(), EnvelopeCounts::class.java) - return response.body!! - } - - fun reset() { - restTemplate().exchange("$baseUrl/reset", HttpMethod.GET, entityWithAuth(), Any::class.java) - } - - fun getEnvelopes(): EnvelopesReceived { - val response = restTemplate().exchange("$baseUrl/envelopes-received", HttpMethod.GET, entityWithAuth(), EnvelopesReceived::class.java) - return response.body!! - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders() - return HttpEntity(request, headers) - } -} - -class EnvelopeCounts { - val envelopes: Long? = null - - override fun toString(): String { - return "EnvelopeCounts{envelopes=$envelopes}" - } -} - -class EnvelopesReceived { - val envelopes: List? = null - - override fun toString(): String { - return "EnvelopesReceived{envelopes=$envelopes}" - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt deleted file mode 100644 index 1017d847348..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt +++ /dev/null @@ -1,173 +0,0 @@ -package io.sentry.systemtest.util - -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Operation -import io.sentry.JsonSerializer -import io.sentry.SentryEvent -import io.sentry.SentryItemType -import io.sentry.SentryOptions -import io.sentry.protocol.SentrySpan -import io.sentry.protocol.SentryTransaction -import io.sentry.systemtest.graphql.GraphqlTestClient -import java.io.PrintWriter -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class TestHelper(backendUrl: String) { - - val restClient: RestTestClient - val graphqlClient: GraphqlTestClient - val sentryClient: SentryMockServerClient - val jsonSerializer: JsonSerializer - - var envelopeCounts: EnvelopeCounts? = null - - init { - restClient = RestTestClient(backendUrl) - sentryClient = SentryMockServerClient("http://localhost:8000") - graphqlClient = GraphqlTestClient(backendUrl) - jsonSerializer = JsonSerializer(SentryOptions.empty()) - } - - fun snapshotEnvelopeCount() { - envelopeCounts = sentryClient.getEnvelopeCount() - } - - fun ensureEnvelopeCountIncreased() { - Thread.sleep(1000) - val envelopeCountsAfter = sentryClient.getEnvelopeCount() - assertTrue(envelopeCountsAfter!!.envelopes!! > envelopeCounts!!.envelopes!!) - } - - fun ensureEnvelopeReceived(callback: ((String) -> Boolean)) { - Thread.sleep(10000) - val envelopes = sentryClient.getEnvelopes() - assertNotNull(envelopes.envelopes) - envelopes.envelopes.forEach { envelopeString -> - val didMatch = callback(envelopeString) - if (didMatch) { - return - } - } - throw RuntimeException("Unable to find matching envelope received by relay") - } - - fun ensureTransactionReceived(callback: ((SentryTransaction) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val transactionItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Transaction } - if (transactionItem == null) { - return@ensureEnvelopeReceived false - } - - val transaction = transactionItem.getTransaction(jsonSerializer) - if (transaction == null) { - return@ensureEnvelopeReceived false - } - - callback(transaction) - } - } - - fun ensureErrorReceived(callback: ((SentryEvent) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val errorItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Event } - if (errorItem == null) { - return@ensureEnvelopeReceived false - } - - val error = errorItem.getEvent(jsonSerializer) - if (error == null) { - return@ensureEnvelopeReceived false - } - - val callbackResult = callback(error) - if (!callbackResult) { - println("found an error event but it did not match:") - logObject(error) - } - callbackResult - } - } - - fun ensureTransactionWithSpanReceived(callback: ((SentrySpan) -> Boolean)) { - ensureTransactionReceived { transaction -> - transaction.spans.forEach { span -> - val callbackResult = callback(span) - if (callbackResult) { - return@ensureTransactionReceived true - } - } - false - } - } - - fun reset() { - sentryClient.reset() - } - - fun logObject(obj: Any?) { - obj ?: return - PrintWriter(System.out).use { - jsonSerializer.serialize(obj, it) - } - } - - fun ensureNoErrors(response: ApolloResponse?) { - response ?: throw RuntimeException("no response") - assertFalse(response.hasErrors()) - } - - fun ensureErrorCount(response: ApolloResponse?, errorCount: Int) { - response ?: throw RuntimeException("no response") - assertEquals(errorCount, response.errors?.size) - } - - fun doesTransactionContainSpanWithOp(transaction: SentryTransaction, op: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.op == op } - if (span == null) { - println("Unable to find span with op $op in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionContainSpanWithDescription(transaction: SentryTransaction, description: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.description == description } - if (span == null) { - println("Unable to find span with description $description in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionHaveOp(transaction: SentryTransaction, op: String): Boolean { - val matches = transaction.contexts.trace?.operation == op - if (!matches) { - println("Unable to find transaction with op $op:") - logObject(transaction) - return false - } - - return true - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts index fcc34c8b5b3..6d390ce15fa 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts @@ -6,7 +6,6 @@ plugins { id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion kotlin("jvm") kotlin("plugin.spring") version Config.kotlinVersion - id("com.apollographql.apollo3") version "3.8.2" } group = "io.sentry.sample.spring-boot-webflux-jakarta" @@ -30,6 +29,7 @@ dependencies { implementation(projects.sentryJdbc) implementation(projects.sentryGraphql22) + testImplementation(projects.sentrySystemTestSupport) testImplementation(Config.Libs.springBoot3StarterTest) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } @@ -77,13 +77,3 @@ tasks.named("test").configure { excludeTestsMatching("io.sentry.systemtest.*") } } - -apollo { - service("service") { - srcDir("src/test/graphql") - packageName.set("io.sentry.samples.graphql") - outputDirConnection { - connectToKotlinSourceSet("test") - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/graphql/greeting.graphql b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/graphql/greeting.graphql deleted file mode 100644 index 06c866a65fa..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/graphql/greeting.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query GreetingQuery($name: String!) { - greeting(name: $name) -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/graphql/schema.graphqls b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/graphql/schema.graphqls deleted file mode 100644 index 111e0f2061c..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/graphql/schema.graphqls +++ /dev/null @@ -1,3 +0,0 @@ -type Query { - greeting(name: String! = "Spring"): String! -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt index 769ae399bf0..5681c421a28 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt @@ -19,7 +19,7 @@ class GraphqlGreetingSystemTest { val response = testHelper.graphqlClient.greet("world") testHelper.ensureNoErrors(response) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } @@ -32,7 +32,7 @@ class GraphqlGreetingSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt index 4708d5609de..f4c3ad40bb1 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt @@ -1,9 +1,7 @@ package io.sentry.systemtest -import io.sentry.samples.spring.boot.jakarta.Person import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -21,9 +19,9 @@ class PersonSystemTest { fun `get person fails`() { val restClient = testHelper.restClient restClient.getPerson(1L) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionHaveOp(transaction, "http.server") } } @@ -33,12 +31,12 @@ class PersonSystemTest { val restClient = testHelper.restClient val person = Person("firstA", "lastB") val returnedPerson = restClient.createPerson(person) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionHaveOp(transaction, "http.server") } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt index fe5c8252ed7..33b7cdeb135 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt @@ -2,7 +2,6 @@ package io.sentry.systemtest import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -20,9 +19,9 @@ class TodoSystemTest { fun `get todo webclient works`() { val restClient = testHelper.restClient restClient.getTodoWebclient(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt deleted file mode 100644 index e8301612aed..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.sentry.systemtest.graphql - -import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Mutation -import com.apollographql.apollo3.api.Query -import io.sentry.samples.graphql.GreetingQuery -import kotlinx.coroutines.runBlocking - -class GraphqlTestClient(backendUrl: String) { - - val apollo = ApolloClient.Builder() - .serverUrl("$backendUrl/graphql") - .addHttpHeader("Authorization", "Basic dXNlcjpwYXNzd29yZA==") - .build() - - fun greet(name: String): ApolloResponse? { - return executeQuery(GreetingQuery(name)) - } - - private fun executeQuery(query: Query): ApolloResponse? = runBlocking { - apollo.query(query).execute() - } - - private fun executeMutation(mutation: Mutation): ApolloResponse? = runBlocking { - apollo.mutation(mutation).execute() - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt deleted file mode 100644 index 17eea1a0084..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.sentry.systemtest.util - -import org.springframework.http.client.BufferingClientHttpRequestFactory -import org.springframework.web.client.RestTemplate - -open class LoggingInsecureRestClient { - - protected fun restTemplate(): RestTemplate { - return RestTemplate().also { - it.requestFactory = BufferingClientHttpRequestFactory(it.requestFactory) - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt deleted file mode 100644 index 9f77b962945..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt +++ /dev/null @@ -1,65 +0,0 @@ -package io.sentry.systemtest.util - -import io.sentry.samples.spring.boot.jakarta.Person -import io.sentry.samples.spring.boot.jakarta.Todo -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.HttpStatusCode -import org.springframework.web.client.HttpStatusCodeException - -class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestClient() { - var lastKnownStatusCode: HttpStatusCode? = null - - fun getPerson(id: Long): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/{id}", HttpMethod.GET, entityWithAuth(), Person::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun createPerson(person: Person): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/", HttpMethod.POST, entityWithAuth(person), Person::class.java, person) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodo(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodoWebclient(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo-webclient/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders().also { - it.setBasicAuth("user", "password") - } - - return HttpEntity(request, headers) - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt deleted file mode 100644 index 7ef1699f122..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.util - -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod - -class SentryMockServerClient(private val baseUrl: String) : LoggingInsecureRestClient() { - - fun getEnvelopeCount(): EnvelopeCounts { - val response = restTemplate().exchange("$baseUrl/envelope-count", HttpMethod.GET, entityWithAuth(), EnvelopeCounts::class.java) - return response.body!! - } - - fun reset() { - restTemplate().exchange("$baseUrl/reset", HttpMethod.GET, entityWithAuth(), Any::class.java) - } - - fun getEnvelopes(): EnvelopesReceived { - val response = restTemplate().exchange("$baseUrl/envelopes-received", HttpMethod.GET, entityWithAuth(), EnvelopesReceived::class.java) - return response.body!! - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders() - return HttpEntity(request, headers) - } -} - -class EnvelopeCounts { - val envelopes: Long? = null - - override fun toString(): String { - return "EnvelopeCounts{envelopes=$envelopes}" - } -} - -class EnvelopesReceived { - val envelopes: List? = null - - override fun toString(): String { - return "EnvelopesReceived{envelopes=$envelopes}" - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt deleted file mode 100644 index 1017d847348..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt +++ /dev/null @@ -1,173 +0,0 @@ -package io.sentry.systemtest.util - -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Operation -import io.sentry.JsonSerializer -import io.sentry.SentryEvent -import io.sentry.SentryItemType -import io.sentry.SentryOptions -import io.sentry.protocol.SentrySpan -import io.sentry.protocol.SentryTransaction -import io.sentry.systemtest.graphql.GraphqlTestClient -import java.io.PrintWriter -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class TestHelper(backendUrl: String) { - - val restClient: RestTestClient - val graphqlClient: GraphqlTestClient - val sentryClient: SentryMockServerClient - val jsonSerializer: JsonSerializer - - var envelopeCounts: EnvelopeCounts? = null - - init { - restClient = RestTestClient(backendUrl) - sentryClient = SentryMockServerClient("http://localhost:8000") - graphqlClient = GraphqlTestClient(backendUrl) - jsonSerializer = JsonSerializer(SentryOptions.empty()) - } - - fun snapshotEnvelopeCount() { - envelopeCounts = sentryClient.getEnvelopeCount() - } - - fun ensureEnvelopeCountIncreased() { - Thread.sleep(1000) - val envelopeCountsAfter = sentryClient.getEnvelopeCount() - assertTrue(envelopeCountsAfter!!.envelopes!! > envelopeCounts!!.envelopes!!) - } - - fun ensureEnvelopeReceived(callback: ((String) -> Boolean)) { - Thread.sleep(10000) - val envelopes = sentryClient.getEnvelopes() - assertNotNull(envelopes.envelopes) - envelopes.envelopes.forEach { envelopeString -> - val didMatch = callback(envelopeString) - if (didMatch) { - return - } - } - throw RuntimeException("Unable to find matching envelope received by relay") - } - - fun ensureTransactionReceived(callback: ((SentryTransaction) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val transactionItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Transaction } - if (transactionItem == null) { - return@ensureEnvelopeReceived false - } - - val transaction = transactionItem.getTransaction(jsonSerializer) - if (transaction == null) { - return@ensureEnvelopeReceived false - } - - callback(transaction) - } - } - - fun ensureErrorReceived(callback: ((SentryEvent) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val errorItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Event } - if (errorItem == null) { - return@ensureEnvelopeReceived false - } - - val error = errorItem.getEvent(jsonSerializer) - if (error == null) { - return@ensureEnvelopeReceived false - } - - val callbackResult = callback(error) - if (!callbackResult) { - println("found an error event but it did not match:") - logObject(error) - } - callbackResult - } - } - - fun ensureTransactionWithSpanReceived(callback: ((SentrySpan) -> Boolean)) { - ensureTransactionReceived { transaction -> - transaction.spans.forEach { span -> - val callbackResult = callback(span) - if (callbackResult) { - return@ensureTransactionReceived true - } - } - false - } - } - - fun reset() { - sentryClient.reset() - } - - fun logObject(obj: Any?) { - obj ?: return - PrintWriter(System.out).use { - jsonSerializer.serialize(obj, it) - } - } - - fun ensureNoErrors(response: ApolloResponse?) { - response ?: throw RuntimeException("no response") - assertFalse(response.hasErrors()) - } - - fun ensureErrorCount(response: ApolloResponse?, errorCount: Int) { - response ?: throw RuntimeException("no response") - assertEquals(errorCount, response.errors?.size) - } - - fun doesTransactionContainSpanWithOp(transaction: SentryTransaction, op: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.op == op } - if (span == null) { - println("Unable to find span with op $op in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionContainSpanWithDescription(transaction: SentryTransaction, description: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.description == description } - if (span == null) { - println("Unable to find span with description $description in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionHaveOp(transaction: SentryTransaction, op: String): Boolean { - val matches = transaction.contexts.trace?.operation == op - if (!matches) { - println("Unable to find transaction with op $op:") - logObject(transaction) - return false - } - - return true - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index b39f32514e7..3e50d013107 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -6,7 +6,6 @@ plugins { id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion kotlin("jvm") kotlin("plugin.spring") version Config.kotlinVersion - id("com.apollographql.apollo3") version "3.8.2" } group = "io.sentry.sample.spring-boot" @@ -27,6 +26,8 @@ dependencies { implementation(projects.sentrySpringBootStarter) implementation(projects.sentryLogback) implementation(projects.sentryGraphql) + + testImplementation(projects.sentrySystemTestSupport) testImplementation(Config.Libs.springBootStarterTest) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } @@ -75,13 +76,3 @@ tasks.named("test").configure { excludeTestsMatching("io.sentry.systemtest.*") } } - -apollo { - service("service") { - srcDir("src/test/graphql") - packageName.set("io.sentry.samples.graphql") - outputDirConnection { - connectToKotlinSourceSet("test") - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/graphql/greeting.graphql b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/graphql/greeting.graphql deleted file mode 100644 index 06c866a65fa..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/graphql/greeting.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query GreetingQuery($name: String!) { - greeting(name: $name) -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/graphql/schema.graphqls b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/graphql/schema.graphqls deleted file mode 100644 index 111e0f2061c..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/graphql/schema.graphqls +++ /dev/null @@ -1,3 +0,0 @@ -type Query { - greeting(name: String! = "Spring"): String! -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt index 769ae399bf0..5681c421a28 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt @@ -19,7 +19,7 @@ class GraphqlGreetingSystemTest { val response = testHelper.graphqlClient.greet("world") testHelper.ensureNoErrors(response) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } @@ -32,7 +32,7 @@ class GraphqlGreetingSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt index e047431ee5b..f4c3ad40bb1 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt @@ -1,9 +1,7 @@ package io.sentry.systemtest -import io.sentry.samples.spring.boot.Person import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -21,9 +19,9 @@ class PersonSystemTest { fun `get person fails`() { val restClient = testHelper.restClient restClient.getPerson(1L) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionHaveOp(transaction, "http.server") } } @@ -33,12 +31,12 @@ class PersonSystemTest { val restClient = testHelper.restClient val person = Person("firstA", "lastB") val returnedPerson = restClient.createPerson(person) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionHaveOp(transaction, "http.server") } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt index fe5c8252ed7..33b7cdeb135 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt @@ -2,7 +2,6 @@ package io.sentry.systemtest import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -20,9 +19,9 @@ class TodoSystemTest { fun `get todo webclient works`() { val restClient = testHelper.restClient restClient.getTodoWebclient(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt deleted file mode 100644 index e8301612aed..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.sentry.systemtest.graphql - -import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Mutation -import com.apollographql.apollo3.api.Query -import io.sentry.samples.graphql.GreetingQuery -import kotlinx.coroutines.runBlocking - -class GraphqlTestClient(backendUrl: String) { - - val apollo = ApolloClient.Builder() - .serverUrl("$backendUrl/graphql") - .addHttpHeader("Authorization", "Basic dXNlcjpwYXNzd29yZA==") - .build() - - fun greet(name: String): ApolloResponse? { - return executeQuery(GreetingQuery(name)) - } - - private fun executeQuery(query: Query): ApolloResponse? = runBlocking { - apollo.query(query).execute() - } - - private fun executeMutation(mutation: Mutation): ApolloResponse? = runBlocking { - apollo.mutation(mutation).execute() - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt deleted file mode 100644 index 0577f0eef21..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt +++ /dev/null @@ -1,32 +0,0 @@ -package io.sentry.systemtest.util - -import org.apache.http.impl.client.HttpClients -import org.springframework.http.client.BufferingClientHttpRequestFactory -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory -import org.springframework.web.client.RestTemplate - -open class LoggingInsecureRestClient { - - protected fun restTemplate(): RestTemplate { - val requestFactory = BufferingClientHttpRequestFactory( - HttpComponentsClientHttpRequestFactory(HttpClients.createDefault()) - ) - return RestTemplate(requestFactory).also { - it.messageConverters.add(0, jacksonConverter()) - } - } - - private fun jacksonConverter(): org.springframework.http.converter.json.MappingJackson2HttpMessageConverter { - val converter = org.springframework.http.converter.json.MappingJackson2HttpMessageConverter() - converter.objectMapper = objectMapper() - return converter - } - - private fun objectMapper(): com.fasterxml.jackson.databind.ObjectMapper { - val builder = org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.json() - val objectMapper: com.fasterxml.jackson.databind.ObjectMapper = builder.createXmlMapper(false).build() - objectMapper.registerModule(com.fasterxml.jackson.datatype.jsr310.JavaTimeModule()) - objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - return objectMapper - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt deleted file mode 100644 index f5d5bd7ee38..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt +++ /dev/null @@ -1,65 +0,0 @@ -package io.sentry.systemtest.util - -import io.sentry.samples.spring.boot.Person -import io.sentry.samples.spring.boot.Todo -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.HttpStatus -import org.springframework.web.client.HttpStatusCodeException - -class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestClient() { - var lastKnownStatusCode: HttpStatus? = null - - fun getPerson(id: Long): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/{id}", HttpMethod.GET, entityWithAuth(), Person::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun createPerson(person: Person): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/", HttpMethod.POST, entityWithAuth(person), Person::class.java, person) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodo(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodoWebclient(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo-webclient/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders().also { - it.setBasicAuth("user", "password") - } - - return HttpEntity(request, headers) - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt deleted file mode 100644 index 7ef1699f122..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.util - -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod - -class SentryMockServerClient(private val baseUrl: String) : LoggingInsecureRestClient() { - - fun getEnvelopeCount(): EnvelopeCounts { - val response = restTemplate().exchange("$baseUrl/envelope-count", HttpMethod.GET, entityWithAuth(), EnvelopeCounts::class.java) - return response.body!! - } - - fun reset() { - restTemplate().exchange("$baseUrl/reset", HttpMethod.GET, entityWithAuth(), Any::class.java) - } - - fun getEnvelopes(): EnvelopesReceived { - val response = restTemplate().exchange("$baseUrl/envelopes-received", HttpMethod.GET, entityWithAuth(), EnvelopesReceived::class.java) - return response.body!! - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders() - return HttpEntity(request, headers) - } -} - -class EnvelopeCounts { - val envelopes: Long? = null - - override fun toString(): String { - return "EnvelopeCounts{envelopes=$envelopes}" - } -} - -class EnvelopesReceived { - val envelopes: List? = null - - override fun toString(): String { - return "EnvelopesReceived{envelopes=$envelopes}" - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt deleted file mode 100644 index 1017d847348..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt +++ /dev/null @@ -1,173 +0,0 @@ -package io.sentry.systemtest.util - -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Operation -import io.sentry.JsonSerializer -import io.sentry.SentryEvent -import io.sentry.SentryItemType -import io.sentry.SentryOptions -import io.sentry.protocol.SentrySpan -import io.sentry.protocol.SentryTransaction -import io.sentry.systemtest.graphql.GraphqlTestClient -import java.io.PrintWriter -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class TestHelper(backendUrl: String) { - - val restClient: RestTestClient - val graphqlClient: GraphqlTestClient - val sentryClient: SentryMockServerClient - val jsonSerializer: JsonSerializer - - var envelopeCounts: EnvelopeCounts? = null - - init { - restClient = RestTestClient(backendUrl) - sentryClient = SentryMockServerClient("http://localhost:8000") - graphqlClient = GraphqlTestClient(backendUrl) - jsonSerializer = JsonSerializer(SentryOptions.empty()) - } - - fun snapshotEnvelopeCount() { - envelopeCounts = sentryClient.getEnvelopeCount() - } - - fun ensureEnvelopeCountIncreased() { - Thread.sleep(1000) - val envelopeCountsAfter = sentryClient.getEnvelopeCount() - assertTrue(envelopeCountsAfter!!.envelopes!! > envelopeCounts!!.envelopes!!) - } - - fun ensureEnvelopeReceived(callback: ((String) -> Boolean)) { - Thread.sleep(10000) - val envelopes = sentryClient.getEnvelopes() - assertNotNull(envelopes.envelopes) - envelopes.envelopes.forEach { envelopeString -> - val didMatch = callback(envelopeString) - if (didMatch) { - return - } - } - throw RuntimeException("Unable to find matching envelope received by relay") - } - - fun ensureTransactionReceived(callback: ((SentryTransaction) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val transactionItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Transaction } - if (transactionItem == null) { - return@ensureEnvelopeReceived false - } - - val transaction = transactionItem.getTransaction(jsonSerializer) - if (transaction == null) { - return@ensureEnvelopeReceived false - } - - callback(transaction) - } - } - - fun ensureErrorReceived(callback: ((SentryEvent) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val errorItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Event } - if (errorItem == null) { - return@ensureEnvelopeReceived false - } - - val error = errorItem.getEvent(jsonSerializer) - if (error == null) { - return@ensureEnvelopeReceived false - } - - val callbackResult = callback(error) - if (!callbackResult) { - println("found an error event but it did not match:") - logObject(error) - } - callbackResult - } - } - - fun ensureTransactionWithSpanReceived(callback: ((SentrySpan) -> Boolean)) { - ensureTransactionReceived { transaction -> - transaction.spans.forEach { span -> - val callbackResult = callback(span) - if (callbackResult) { - return@ensureTransactionReceived true - } - } - false - } - } - - fun reset() { - sentryClient.reset() - } - - fun logObject(obj: Any?) { - obj ?: return - PrintWriter(System.out).use { - jsonSerializer.serialize(obj, it) - } - } - - fun ensureNoErrors(response: ApolloResponse?) { - response ?: throw RuntimeException("no response") - assertFalse(response.hasErrors()) - } - - fun ensureErrorCount(response: ApolloResponse?, errorCount: Int) { - response ?: throw RuntimeException("no response") - assertEquals(errorCount, response.errors?.size) - } - - fun doesTransactionContainSpanWithOp(transaction: SentryTransaction, op: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.op == op } - if (span == null) { - println("Unable to find span with op $op in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionContainSpanWithDescription(transaction: SentryTransaction, description: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.description == description } - if (span == null) { - println("Unable to find span with description $description in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionHaveOp(transaction: SentryTransaction, op: String): Boolean { - val matches = transaction.contexts.trace?.operation == op - if (!matches) { - println("Unable to find transaction with op $op:") - logObject(transaction) - return false - } - - return true - } -} diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 9c9f090885f..3ffdcc8bef0 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -6,7 +6,6 @@ plugins { id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion kotlin("jvm") kotlin("plugin.spring") version Config.kotlinVersion - id("com.apollographql.apollo3") version "3.8.2" } group = "io.sentry.sample.spring-boot" @@ -56,6 +55,7 @@ dependencies { // database query tracing implementation(projects.sentryJdbc) runtimeOnly(Config.TestLibs.hsqldb) + testImplementation(projects.sentrySystemTestSupport) testImplementation(Config.Libs.springBootStarterTest) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } @@ -97,13 +97,3 @@ tasks.named("test").configure { excludeTestsMatching("io.sentry.systemtest.*") } } - -apollo { - service("service") { - srcDir("src/test/graphql") - packageName.set("io.sentry.samples.graphql") - outputDirConnection { - connectToKotlinSourceSet("test") - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/graphql/greeting.graphql b/sentry-samples/sentry-samples-spring-boot/src/test/graphql/greeting.graphql deleted file mode 100644 index 06c866a65fa..00000000000 --- a/sentry-samples/sentry-samples-spring-boot/src/test/graphql/greeting.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query GreetingQuery($name: String!) { - greeting(name: $name) -} diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/graphql/project.graphql b/sentry-samples/sentry-samples-spring-boot/src/test/graphql/project.graphql deleted file mode 100644 index bff62ed2c2c..00000000000 --- a/sentry-samples/sentry-samples-spring-boot/src/test/graphql/project.graphql +++ /dev/null @@ -1,11 +0,0 @@ -query ProjectQuery($slug: ID!) { - project(slug: $slug) { - slug - name - status - } -} - -mutation AddProjectMutation($slug: ID!) { - addProject(slug: $slug) -} diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/graphql/schema.graphqls b/sentry-samples/sentry-samples-spring-boot/src/test/graphql/schema.graphqls deleted file mode 100644 index d76aca4756a..00000000000 --- a/sentry-samples/sentry-samples-spring-boot/src/test/graphql/schema.graphqls +++ /dev/null @@ -1,70 +0,0 @@ -type Query { - greeting(name: String! = "Spring"): String! - project(slug: ID!): Project - tasks(projectSlug: ID!): [Task] -} - -type Mutation { - addProject(slug: ID!): String! -} - -type Subscription { - notifyNewTask(projectSlug: ID!): Task -} - -""" A Project in the Spring portfolio """ -type Project { - """ Unique string id used in URLs """ - slug: ID! - """ Project name """ - name: String - """ URL of the git repository """ - repositoryUrl: String! - """ Current support status """ - status: ProjectStatus! -} - -""" A task """ -type Task { - """ ID """ - id: String! - """ Name """ - name: String! - """ ID of the Assignee """ - assigneeId: String - """ Assignee """ - assignee: Assignee - """ ID of the Creator """ - creatorId: String - """ Creator """ - creator: Creator -} - -""" An Assignee """ -type Assignee { - """ ID """ - id: String! - """ Name """ - name: String! -} - -""" An Creator """ -type Creator { - """ ID """ - id: String! - """ Name """ - name: String! -} - -enum ProjectStatus { - """ Actively supported by the Spring team """ - ACTIVE - """ Supported by the community """ - COMMUNITY - """ Prototype, not officially supported yet """ - INCUBATING - """ Project being retired, in maintenance mode """ - ATTIC - """ End-Of-Lifed """ - EOL -} diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/graphql/task.graphql b/sentry-samples/sentry-samples-spring-boot/src/test/graphql/task.graphql deleted file mode 100644 index 11ae18574d3..00000000000 --- a/sentry-samples/sentry-samples-spring-boot/src/test/graphql/task.graphql +++ /dev/null @@ -1,16 +0,0 @@ -query TasksAndAssigneesQuery($slug: ID!) { - tasks(projectSlug: $slug) { - id - name - assigneeId - assignee { - id - name - } - creatorId - creator { - id - name - } - } -} diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt index 769ae399bf0..5681c421a28 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlGreetingSystemTest.kt @@ -19,7 +19,7 @@ class GraphqlGreetingSystemTest { val response = testHelper.graphqlClient.greet("world") testHelper.ensureNoErrors(response) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } @@ -32,7 +32,7 @@ class GraphqlGreetingSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.greeting") } } diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt index 74b196e33b6..bfa38fead33 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlProjectSystemTest.kt @@ -23,7 +23,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertEquals("proj-slug", response?.data?.project?.slug) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.project") } } @@ -34,7 +34,7 @@ class GraphqlProjectSystemTest { testHelper.ensureNoErrors(response) assertNotNull(response?.data?.addProject) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Mutation.addProject") } } @@ -48,7 +48,7 @@ class GraphqlProjectSystemTest { testHelper.ensureErrorReceived { error -> error.message?.message?.startsWith("Unresolved RuntimeException for executionId ") ?: false } - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Mutation.addProject") } } diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt index cf2aebfd090..0f634f309bd 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/GraphqlTaskSystemTest.kt @@ -30,7 +30,7 @@ class GraphqlTaskSystemTest { assertEquals("C3", firstTask.creatorId) assertEquals("C3", firstTask.creator?.id) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithDescription(transaction, "Query.tasks") && testHelper.doesTransactionContainSpanWithDescription(transaction, "Task.assignee") && testHelper.doesTransactionContainSpanWithDescription(transaction, "Task.creator") diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt index 96b0860856a..c190b86d6b0 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt @@ -1,9 +1,7 @@ package io.sentry.systemtest -import io.sentry.samples.spring.boot.Person import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -21,9 +19,9 @@ class PersonSystemTest { fun `get person fails`() { val restClient = testHelper.restClient restClient.getPerson(1L) - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, restClient.lastKnownStatusCode) + assertEquals(500, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionHaveOp(transaction, "http.server") } } @@ -33,12 +31,12 @@ class PersonSystemTest { val restClient = testHelper.restClient val person = Person("firstA", "lastB") val returnedPerson = restClient.createPerson(person) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) assertEquals(person.firstName, returnedPerson!!.firstName) assertEquals(person.lastName, returnedPerson!!.lastName) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "PersonService.create") && testHelper.doesTransactionContainSpanWithOp(transaction, "db.query") } diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt index 7a756d98da0..02afc3e0ad6 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt +++ b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/TodoSystemTest.kt @@ -2,7 +2,6 @@ package io.sentry.systemtest import io.sentry.systemtest.util.TestHelper import org.junit.Before -import org.springframework.http.HttpStatus import kotlin.test.Test import kotlin.test.assertEquals @@ -20,9 +19,9 @@ class TodoSystemTest { fun `get todo works`() { val restClient = testHelper.restClient restClient.getTodo(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") } } @@ -31,9 +30,9 @@ class TodoSystemTest { fun `get todo webclient works`() { val restClient = testHelper.restClient restClient.getTodoWebclient(1L) - assertEquals(HttpStatus.OK, restClient.lastKnownStatusCode) + assertEquals(200, restClient.lastKnownStatusCode) - testHelper.ensureTransactionReceived { transaction -> + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> testHelper.doesTransactionContainSpanWithOp(transaction, "http.client") } } diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt deleted file mode 100644 index 0c11906292b..00000000000 --- a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.graphql - -import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Mutation -import com.apollographql.apollo3.api.Query -import io.sentry.samples.graphql.AddProjectMutation -import io.sentry.samples.graphql.GreetingQuery -import io.sentry.samples.graphql.ProjectQuery -import io.sentry.samples.graphql.TasksAndAssigneesQuery -import kotlinx.coroutines.runBlocking - -class GraphqlTestClient(backendUrl: String) { - - val apollo = ApolloClient.Builder() - .serverUrl("$backendUrl/graphql") - .addHttpHeader("Authorization", "Basic dXNlcjpwYXNzd29yZA==") - .build() - - fun greet(name: String): ApolloResponse? { - return executeQuery(GreetingQuery(name)) - } - - fun project(slug: String): ApolloResponse? { - return executeQuery(ProjectQuery(slug)) - } - - fun tasksAndAssignees(slug: String): ApolloResponse? { - return executeQuery(TasksAndAssigneesQuery(slug)) - } - - fun addProject(slug: String): ApolloResponse? { - return executeMutation(AddProjectMutation(slug)) - } - - private fun executeQuery(query: Query): ApolloResponse? = runBlocking { - apollo.query(query).execute() - } - - private fun executeMutation(mutation: Mutation): ApolloResponse? = runBlocking { - apollo.mutation(mutation).execute() - } -} diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt deleted file mode 100644 index 0577f0eef21..00000000000 --- a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt +++ /dev/null @@ -1,32 +0,0 @@ -package io.sentry.systemtest.util - -import org.apache.http.impl.client.HttpClients -import org.springframework.http.client.BufferingClientHttpRequestFactory -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory -import org.springframework.web.client.RestTemplate - -open class LoggingInsecureRestClient { - - protected fun restTemplate(): RestTemplate { - val requestFactory = BufferingClientHttpRequestFactory( - HttpComponentsClientHttpRequestFactory(HttpClients.createDefault()) - ) - return RestTemplate(requestFactory).also { - it.messageConverters.add(0, jacksonConverter()) - } - } - - private fun jacksonConverter(): org.springframework.http.converter.json.MappingJackson2HttpMessageConverter { - val converter = org.springframework.http.converter.json.MappingJackson2HttpMessageConverter() - converter.objectMapper = objectMapper() - return converter - } - - private fun objectMapper(): com.fasterxml.jackson.databind.ObjectMapper { - val builder = org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.json() - val objectMapper: com.fasterxml.jackson.databind.ObjectMapper = builder.createXmlMapper(false).build() - objectMapper.registerModule(com.fasterxml.jackson.datatype.jsr310.JavaTimeModule()) - objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - return objectMapper - } -} diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt deleted file mode 100644 index f5d5bd7ee38..00000000000 --- a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt +++ /dev/null @@ -1,65 +0,0 @@ -package io.sentry.systemtest.util - -import io.sentry.samples.spring.boot.Person -import io.sentry.samples.spring.boot.Todo -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.HttpStatus -import org.springframework.web.client.HttpStatusCodeException - -class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestClient() { - var lastKnownStatusCode: HttpStatus? = null - - fun getPerson(id: Long): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/{id}", HttpMethod.GET, entityWithAuth(), Person::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun createPerson(person: Person): Person? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/person/", HttpMethod.POST, entityWithAuth(person), Person::class.java, person) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodo(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - fun getTodoWebclient(id: Long): Todo? { - return try { - val response = restTemplate().exchange("$backendBaseUrl/todo-webclient/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode - response.body - } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode - null - } - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders().also { - it.setBasicAuth("user", "password") - } - - return HttpEntity(request, headers) - } -} diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt deleted file mode 100644 index 7ef1699f122..00000000000 --- a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.sentry.systemtest.util - -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod - -class SentryMockServerClient(private val baseUrl: String) : LoggingInsecureRestClient() { - - fun getEnvelopeCount(): EnvelopeCounts { - val response = restTemplate().exchange("$baseUrl/envelope-count", HttpMethod.GET, entityWithAuth(), EnvelopeCounts::class.java) - return response.body!! - } - - fun reset() { - restTemplate().exchange("$baseUrl/reset", HttpMethod.GET, entityWithAuth(), Any::class.java) - } - - fun getEnvelopes(): EnvelopesReceived { - val response = restTemplate().exchange("$baseUrl/envelopes-received", HttpMethod.GET, entityWithAuth(), EnvelopesReceived::class.java) - return response.body!! - } - - private fun entityWithAuth(request: Any? = null): HttpEntity { - val headers = HttpHeaders() - return HttpEntity(request, headers) - } -} - -class EnvelopeCounts { - val envelopes: Long? = null - - override fun toString(): String { - return "EnvelopeCounts{envelopes=$envelopes}" - } -} - -class EnvelopesReceived { - val envelopes: List? = null - - override fun toString(): String { - return "EnvelopesReceived{envelopes=$envelopes}" - } -} diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt deleted file mode 100644 index 1017d847348..00000000000 --- a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt +++ /dev/null @@ -1,173 +0,0 @@ -package io.sentry.systemtest.util - -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Operation -import io.sentry.JsonSerializer -import io.sentry.SentryEvent -import io.sentry.SentryItemType -import io.sentry.SentryOptions -import io.sentry.protocol.SentrySpan -import io.sentry.protocol.SentryTransaction -import io.sentry.systemtest.graphql.GraphqlTestClient -import java.io.PrintWriter -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class TestHelper(backendUrl: String) { - - val restClient: RestTestClient - val graphqlClient: GraphqlTestClient - val sentryClient: SentryMockServerClient - val jsonSerializer: JsonSerializer - - var envelopeCounts: EnvelopeCounts? = null - - init { - restClient = RestTestClient(backendUrl) - sentryClient = SentryMockServerClient("http://localhost:8000") - graphqlClient = GraphqlTestClient(backendUrl) - jsonSerializer = JsonSerializer(SentryOptions.empty()) - } - - fun snapshotEnvelopeCount() { - envelopeCounts = sentryClient.getEnvelopeCount() - } - - fun ensureEnvelopeCountIncreased() { - Thread.sleep(1000) - val envelopeCountsAfter = sentryClient.getEnvelopeCount() - assertTrue(envelopeCountsAfter!!.envelopes!! > envelopeCounts!!.envelopes!!) - } - - fun ensureEnvelopeReceived(callback: ((String) -> Boolean)) { - Thread.sleep(10000) - val envelopes = sentryClient.getEnvelopes() - assertNotNull(envelopes.envelopes) - envelopes.envelopes.forEach { envelopeString -> - val didMatch = callback(envelopeString) - if (didMatch) { - return - } - } - throw RuntimeException("Unable to find matching envelope received by relay") - } - - fun ensureTransactionReceived(callback: ((SentryTransaction) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val transactionItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Transaction } - if (transactionItem == null) { - return@ensureEnvelopeReceived false - } - - val transaction = transactionItem.getTransaction(jsonSerializer) - if (transaction == null) { - return@ensureEnvelopeReceived false - } - - callback(transaction) - } - } - - fun ensureErrorReceived(callback: ((SentryEvent) -> Boolean)) { - ensureEnvelopeReceived { envelopeString -> - val deserializeEnvelope = - jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream()) - if (deserializeEnvelope == null) { - return@ensureEnvelopeReceived false - } - - val errorItem = - deserializeEnvelope.items.firstOrNull { it.header.type == SentryItemType.Event } - if (errorItem == null) { - return@ensureEnvelopeReceived false - } - - val error = errorItem.getEvent(jsonSerializer) - if (error == null) { - return@ensureEnvelopeReceived false - } - - val callbackResult = callback(error) - if (!callbackResult) { - println("found an error event but it did not match:") - logObject(error) - } - callbackResult - } - } - - fun ensureTransactionWithSpanReceived(callback: ((SentrySpan) -> Boolean)) { - ensureTransactionReceived { transaction -> - transaction.spans.forEach { span -> - val callbackResult = callback(span) - if (callbackResult) { - return@ensureTransactionReceived true - } - } - false - } - } - - fun reset() { - sentryClient.reset() - } - - fun logObject(obj: Any?) { - obj ?: return - PrintWriter(System.out).use { - jsonSerializer.serialize(obj, it) - } - } - - fun ensureNoErrors(response: ApolloResponse?) { - response ?: throw RuntimeException("no response") - assertFalse(response.hasErrors()) - } - - fun ensureErrorCount(response: ApolloResponse?, errorCount: Int) { - response ?: throw RuntimeException("no response") - assertEquals(errorCount, response.errors?.size) - } - - fun doesTransactionContainSpanWithOp(transaction: SentryTransaction, op: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.op == op } - if (span == null) { - println("Unable to find span with op $op in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionContainSpanWithDescription(transaction: SentryTransaction, description: String): Boolean { - val span = transaction.spans.firstOrNull { span -> span.description == description } - if (span == null) { - println("Unable to find span with description $description in transaction:") - logObject(transaction) - return false - } - - return true - } - - fun doesTransactionHaveOp(transaction: SentryTransaction, op: String): Boolean { - val matches = transaction.contexts.trace?.operation == op - if (!matches) { - println("Unable to find transaction with op $op:") - logObject(transaction) - return false - } - - return true - } -} diff --git a/sentry-system-test-support/api/sentry-system-test-support.api b/sentry-system-test-support/api/sentry-system-test-support.api new file mode 100644 index 00000000000..f91725edfbf --- /dev/null +++ b/sentry-system-test-support/api/sentry-system-test-support.api @@ -0,0 +1,572 @@ +public final class io/sentry/samples/graphql/AddProjectMutation : com/apollographql/apollo3/api/Mutation { + public static final field Companion Lio/sentry/samples/graphql/AddProjectMutation$Companion; + public static final field OPERATION_ID Ljava/lang/String; + public static final field OPERATION_NAME Ljava/lang/String; + public fun (Ljava/lang/String;)V + public fun adapter ()Lcom/apollographql/apollo3/api/Adapter; + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/AddProjectMutation; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/AddProjectMutation;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/AddProjectMutation; + public fun document ()Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public final fun getSlug ()Ljava/lang/String; + public fun hashCode ()I + public fun id ()Ljava/lang/String; + public fun name ()Ljava/lang/String; + public fun rootField ()Lcom/apollographql/apollo3/api/CompiledField; + public fun serializeVariables (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)V + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/AddProjectMutation$Companion { + public final fun getOPERATION_DOCUMENT ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/AddProjectMutation$Data : com/apollographql/apollo3/api/Mutation$Data { + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/AddProjectMutation$Data; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/AddProjectMutation$Data;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/AddProjectMutation$Data; + public fun equals (Ljava/lang/Object;)Z + public final fun getAddProject ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/GreetingQuery : com/apollographql/apollo3/api/Query { + public static final field Companion Lio/sentry/samples/graphql/GreetingQuery$Companion; + public static final field OPERATION_ID Ljava/lang/String; + public static final field OPERATION_NAME Ljava/lang/String; + public fun (Ljava/lang/String;)V + public fun adapter ()Lcom/apollographql/apollo3/api/Adapter; + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/GreetingQuery; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/GreetingQuery;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/GreetingQuery; + public fun document ()Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun id ()Ljava/lang/String; + public fun name ()Ljava/lang/String; + public fun rootField ()Lcom/apollographql/apollo3/api/CompiledField; + public fun serializeVariables (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)V + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/GreetingQuery$Companion { + public final fun getOPERATION_DOCUMENT ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/GreetingQuery$Data : com/apollographql/apollo3/api/Query$Data { + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/GreetingQuery$Data; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/GreetingQuery$Data;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/GreetingQuery$Data; + public fun equals (Ljava/lang/Object;)Z + public final fun getGreeting ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/ProjectQuery : com/apollographql/apollo3/api/Query { + public static final field Companion Lio/sentry/samples/graphql/ProjectQuery$Companion; + public static final field OPERATION_ID Ljava/lang/String; + public static final field OPERATION_NAME Ljava/lang/String; + public fun (Ljava/lang/String;)V + public fun adapter ()Lcom/apollographql/apollo3/api/Adapter; + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/ProjectQuery; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/ProjectQuery;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/ProjectQuery; + public fun document ()Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public final fun getSlug ()Ljava/lang/String; + public fun hashCode ()I + public fun id ()Ljava/lang/String; + public fun name ()Ljava/lang/String; + public fun rootField ()Lcom/apollographql/apollo3/api/CompiledField; + public fun serializeVariables (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)V + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/ProjectQuery$Companion { + public final fun getOPERATION_DOCUMENT ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/ProjectQuery$Data : com/apollographql/apollo3/api/Query$Data { + public fun (Lio/sentry/samples/graphql/ProjectQuery$Project;)V + public final fun component1 ()Lio/sentry/samples/graphql/ProjectQuery$Project; + public final fun copy (Lio/sentry/samples/graphql/ProjectQuery$Project;)Lio/sentry/samples/graphql/ProjectQuery$Data; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/ProjectQuery$Data;Lio/sentry/samples/graphql/ProjectQuery$Project;ILjava/lang/Object;)Lio/sentry/samples/graphql/ProjectQuery$Data; + public fun equals (Ljava/lang/Object;)Z + public final fun getProject ()Lio/sentry/samples/graphql/ProjectQuery$Project; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/ProjectQuery$Project { + public fun (Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/type/ProjectStatus;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/sentry/samples/graphql/type/ProjectStatus; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/type/ProjectStatus;)Lio/sentry/samples/graphql/ProjectQuery$Project; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/ProjectQuery$Project;Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/type/ProjectStatus;ILjava/lang/Object;)Lio/sentry/samples/graphql/ProjectQuery$Project; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getSlug ()Ljava/lang/String; + public final fun getStatus ()Lio/sentry/samples/graphql/type/ProjectStatus; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/TasksAndAssigneesQuery : com/apollographql/apollo3/api/Query { + public static final field Companion Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Companion; + public static final field OPERATION_ID Ljava/lang/String; + public static final field OPERATION_NAME Ljava/lang/String; + public fun (Ljava/lang/String;)V + public fun adapter ()Lcom/apollographql/apollo3/api/Adapter; + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/TasksAndAssigneesQuery;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery; + public fun document ()Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public final fun getSlug ()Ljava/lang/String; + public fun hashCode ()I + public fun id ()Ljava/lang/String; + public fun name ()Ljava/lang/String; + public fun rootField ()Lcom/apollographql/apollo3/api/CompiledField; + public fun serializeVariables (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)V + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/TasksAndAssigneesQuery$Companion { + public final fun getOPERATION_DOCUMENT ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/TasksAndAssigneesQuery$Creator { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/TasksAndAssigneesQuery$Data : com/apollographql/apollo3/api/Query$Data { + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Data; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Data;Ljava/util/List;ILjava/lang/Object;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Data; + public fun equals (Ljava/lang/Object;)Z + public final fun getTasks ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/TasksAndAssigneesQuery$Task { + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Task; + public static synthetic fun copy$default (Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Task;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator;ILjava/lang/Object;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Task; + public fun equals (Ljava/lang/Object;)Z + public final fun getAssignee ()Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee; + public final fun getAssigneeId ()Ljava/lang/String; + public final fun getCreator ()Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator; + public final fun getCreatorId ()Ljava/lang/String; + public final fun getId ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/samples/graphql/adapter/AddProjectMutation_ResponseAdapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/AddProjectMutation_ResponseAdapter; +} + +public final class io/sentry/samples/graphql/adapter/AddProjectMutation_ResponseAdapter$Data : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/AddProjectMutation_ResponseAdapter$Data; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/AddProjectMutation$Data; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public final fun getRESPONSE_NAMES ()Ljava/util/List; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/AddProjectMutation$Data;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/adapter/AddProjectMutation_VariablesAdapter : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/AddProjectMutation_VariablesAdapter; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/AddProjectMutation; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/AddProjectMutation;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/adapter/GreetingQuery_ResponseAdapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/GreetingQuery_ResponseAdapter; +} + +public final class io/sentry/samples/graphql/adapter/GreetingQuery_ResponseAdapter$Data : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/GreetingQuery_ResponseAdapter$Data; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/GreetingQuery$Data; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public final fun getRESPONSE_NAMES ()Ljava/util/List; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/GreetingQuery$Data;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/adapter/GreetingQuery_VariablesAdapter : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/GreetingQuery_VariablesAdapter; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/GreetingQuery; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/GreetingQuery;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter; +} + +public final class io/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter$Data : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter$Data; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/ProjectQuery$Data; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public final fun getRESPONSE_NAMES ()Ljava/util/List; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/ProjectQuery$Data;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter$Project : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter$Project; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/ProjectQuery$Project; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public final fun getRESPONSE_NAMES ()Ljava/util/List; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/ProjectQuery$Project;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/adapter/ProjectQuery_VariablesAdapter : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/ProjectQuery_VariablesAdapter; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/ProjectQuery; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/ProjectQuery;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter; +} + +public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Assignee : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Assignee; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public final fun getRESPONSE_NAMES ()Ljava/util/List; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Creator : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Creator; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public final fun getRESPONSE_NAMES ()Ljava/util/List; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Data : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Data; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Data; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public final fun getRESPONSE_NAMES ()Ljava/util/List; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Data;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Task : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Task; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Task; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public final fun getRESPONSE_NAMES ()Ljava/util/List; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Task;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_VariablesAdapter : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_VariablesAdapter; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/TasksAndAssigneesQuery;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/samples/graphql/selections/AddProjectMutationSelections { + public static final field INSTANCE Lio/sentry/samples/graphql/selections/AddProjectMutationSelections; + public final fun get__root ()Ljava/util/List; +} + +public final class io/sentry/samples/graphql/selections/GreetingQuerySelections { + public static final field INSTANCE Lio/sentry/samples/graphql/selections/GreetingQuerySelections; + public final fun get__root ()Ljava/util/List; +} + +public final class io/sentry/samples/graphql/selections/ProjectQuerySelections { + public static final field INSTANCE Lio/sentry/samples/graphql/selections/ProjectQuerySelections; + public final fun get__root ()Ljava/util/List; +} + +public final class io/sentry/samples/graphql/selections/TasksAndAssigneesQuerySelections { + public static final field INSTANCE Lio/sentry/samples/graphql/selections/TasksAndAssigneesQuerySelections; + public final fun get__root ()Ljava/util/List; +} + +public final class io/sentry/samples/graphql/type/Assignee { + public static final field Companion Lio/sentry/samples/graphql/type/Assignee$Companion; + public fun ()V +} + +public final class io/sentry/samples/graphql/type/Assignee$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; +} + +public final class io/sentry/samples/graphql/type/Creator { + public static final field Companion Lio/sentry/samples/graphql/type/Creator$Companion; + public fun ()V +} + +public final class io/sentry/samples/graphql/type/Creator$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; +} + +public final class io/sentry/samples/graphql/type/GraphQLBoolean { + public static final field Companion Lio/sentry/samples/graphql/type/GraphQLBoolean$Companion; + public fun ()V +} + +public final class io/sentry/samples/graphql/type/GraphQLBoolean$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/CustomScalarType; +} + +public final class io/sentry/samples/graphql/type/GraphQLFloat { + public static final field Companion Lio/sentry/samples/graphql/type/GraphQLFloat$Companion; + public fun ()V +} + +public final class io/sentry/samples/graphql/type/GraphQLFloat$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/CustomScalarType; +} + +public final class io/sentry/samples/graphql/type/GraphQLID { + public static final field Companion Lio/sentry/samples/graphql/type/GraphQLID$Companion; + public fun ()V +} + +public final class io/sentry/samples/graphql/type/GraphQLID$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/CustomScalarType; +} + +public final class io/sentry/samples/graphql/type/GraphQLInt { + public static final field Companion Lio/sentry/samples/graphql/type/GraphQLInt$Companion; + public fun ()V +} + +public final class io/sentry/samples/graphql/type/GraphQLInt$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/CustomScalarType; +} + +public final class io/sentry/samples/graphql/type/GraphQLString { + public static final field Companion Lio/sentry/samples/graphql/type/GraphQLString$Companion; + public fun ()V +} + +public final class io/sentry/samples/graphql/type/GraphQLString$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/CustomScalarType; +} + +public final class io/sentry/samples/graphql/type/Mutation { + public static final field Companion Lio/sentry/samples/graphql/type/Mutation$Companion; + public fun ()V +} + +public final class io/sentry/samples/graphql/type/Mutation$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; +} + +public final class io/sentry/samples/graphql/type/Project { + public static final field Companion Lio/sentry/samples/graphql/type/Project$Companion; + public fun ()V +} + +public final class io/sentry/samples/graphql/type/Project$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; +} + +public final class io/sentry/samples/graphql/type/ProjectStatus : java/lang/Enum { + public static final field ACTIVE Lio/sentry/samples/graphql/type/ProjectStatus; + public static final field ATTIC Lio/sentry/samples/graphql/type/ProjectStatus; + public static final field COMMUNITY Lio/sentry/samples/graphql/type/ProjectStatus; + public static final field Companion Lio/sentry/samples/graphql/type/ProjectStatus$Companion; + public static final field EOL Lio/sentry/samples/graphql/type/ProjectStatus; + public static final field INCUBATING Lio/sentry/samples/graphql/type/ProjectStatus; + public static final field UNKNOWN__ Lio/sentry/samples/graphql/type/ProjectStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getRawValue ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lio/sentry/samples/graphql/type/ProjectStatus; + public static fun values ()[Lio/sentry/samples/graphql/type/ProjectStatus; +} + +public final class io/sentry/samples/graphql/type/ProjectStatus$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/EnumType; + public final fun knownValues ()[Lio/sentry/samples/graphql/type/ProjectStatus; + public final fun safeValueOf (Ljava/lang/String;)Lio/sentry/samples/graphql/type/ProjectStatus; +} + +public final class io/sentry/samples/graphql/type/Query { + public static final field Companion Lio/sentry/samples/graphql/type/Query$Companion; + public fun ()V +} + +public final class io/sentry/samples/graphql/type/Query$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; +} + +public final class io/sentry/samples/graphql/type/Task { + public static final field Companion Lio/sentry/samples/graphql/type/Task$Companion; + public fun ()V +} + +public final class io/sentry/samples/graphql/type/Task$Companion { + public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; +} + +public final class io/sentry/samples/graphql/type/adapter/ProjectStatus_ResponseAdapter : com/apollographql/apollo3/api/Adapter { + public static final field INSTANCE Lio/sentry/samples/graphql/type/adapter/ProjectStatus_ResponseAdapter; + public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/type/ProjectStatus; + public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; + public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/type/ProjectStatus;)V + public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V +} + +public final class io/sentry/systemtest/Person { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/systemtest/Person; + public static synthetic fun copy$default (Lio/sentry/systemtest/Person;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/systemtest/Person; + public fun equals (Ljava/lang/Object;)Z + public final fun getFirstName ()Ljava/lang/String; + public final fun getLastName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/systemtest/Todo { + public fun (JLjava/lang/String;Z)V + public final fun component1 ()J + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Z + public final fun copy (JLjava/lang/String;Z)Lio/sentry/systemtest/Todo; + public static synthetic fun copy$default (Lio/sentry/systemtest/Todo;JLjava/lang/String;ZILjava/lang/Object;)Lio/sentry/systemtest/Todo; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()J + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public final fun isCompleted ()Z + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/systemtest/graphql/GraphqlTestClient { + public fun (Ljava/lang/String;)V + public final fun addProject (Ljava/lang/String;)Lcom/apollographql/apollo3/api/ApolloResponse; + public final fun getApollo ()Lcom/apollographql/apollo3/ApolloClient; + public final fun greet (Ljava/lang/String;)Lcom/apollographql/apollo3/api/ApolloResponse; + public final fun project (Ljava/lang/String;)Lcom/apollographql/apollo3/api/ApolloResponse; + public final fun tasksAndAssignees (Ljava/lang/String;)Lcom/apollographql/apollo3/api/ApolloResponse; +} + +public final class io/sentry/systemtest/util/EnvelopeCounts { + public fun ()V + public final fun getEnvelopes ()Ljava/lang/Long; + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/systemtest/util/EnvelopesReceived { + public fun ()V + public final fun getEnvelopes ()Ljava/util/List; + public fun toString ()Ljava/lang/String; +} + +public class io/sentry/systemtest/util/LoggingInsecureRestClient { + public fun ()V + protected final fun restTemplate ()Lorg/springframework/web/client/RestTemplate; +} + +public final class io/sentry/systemtest/util/RestTestClient : io/sentry/systemtest/util/LoggingInsecureRestClient { + public fun (Ljava/lang/String;)V + public final fun createPerson (Lio/sentry/systemtest/Person;Ljava/util/Map;)Lio/sentry/systemtest/Person; + public static synthetic fun createPerson$default (Lio/sentry/systemtest/util/RestTestClient;Lio/sentry/systemtest/Person;Ljava/util/Map;ILjava/lang/Object;)Lio/sentry/systemtest/Person; + public final fun createPersonDistributedTracing (Lio/sentry/systemtest/Person;Ljava/util/Map;)Lio/sentry/systemtest/Person; + public static synthetic fun createPersonDistributedTracing$default (Lio/sentry/systemtest/util/RestTestClient;Lio/sentry/systemtest/Person;Ljava/util/Map;ILjava/lang/Object;)Lio/sentry/systemtest/Person; + public final fun getLastKnownStatusCode ()Ljava/lang/Integer; + public final fun getPerson (J)Lio/sentry/systemtest/Person; + public final fun getPersonDistributedTracing (JLjava/util/Map;)Lio/sentry/systemtest/Person; + public static synthetic fun getPersonDistributedTracing$default (Lio/sentry/systemtest/util/RestTestClient;JLjava/util/Map;ILjava/lang/Object;)Lio/sentry/systemtest/Person; + public final fun getTodo (J)Lio/sentry/systemtest/Todo; + public final fun getTodoRestClient (J)Lio/sentry/systemtest/Todo; + public final fun getTodoWebclient (J)Lio/sentry/systemtest/Todo; + public final fun setLastKnownStatusCode (Ljava/lang/Integer;)V +} + +public final class io/sentry/systemtest/util/SentryMockServerClient : io/sentry/systemtest/util/LoggingInsecureRestClient { + public fun (Ljava/lang/String;)V + public final fun getEnvelopeCount ()Lio/sentry/systemtest/util/EnvelopeCounts; + public final fun getEnvelopes ()Lio/sentry/systemtest/util/EnvelopesReceived; + public final fun reset ()V +} + +public final class io/sentry/systemtest/util/TestHelper { + public fun (Ljava/lang/String;)V + public final fun doesTransactionContainSpanWithDescription (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;)Z + public final fun doesTransactionContainSpanWithOp (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;)Z + public final fun doesTransactionHaveOp (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;)Z + public final fun doesTransactionHaveTraceId (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;)Z + public final fun ensureEnvelopeCountIncreased ()V + public final fun ensureEnvelopeReceived (Lkotlin/jvm/functions/Function1;)V + public final fun ensureErrorCount (Lcom/apollographql/apollo3/api/ApolloResponse;I)V + public final fun ensureErrorReceived (Lkotlin/jvm/functions/Function1;)V + public final fun ensureNoEnvelopeReceived (Lkotlin/jvm/functions/Function1;)V + public final fun ensureNoErrors (Lcom/apollographql/apollo3/api/ApolloResponse;)V + public final fun ensureNoTransactionReceived (Lkotlin/jvm/functions/Function2;)V + public final fun ensureTransactionReceived (Lkotlin/jvm/functions/Function2;)V + public final fun ensureTransactionWithSpanReceived (Lkotlin/jvm/functions/Function1;)V + public final fun getEnvelopeCounts ()Lio/sentry/systemtest/util/EnvelopeCounts; + public final fun getGraphqlClient ()Lio/sentry/systemtest/graphql/GraphqlTestClient; + public final fun getJsonSerializer ()Lio/sentry/JsonSerializer; + public final fun getRestClient ()Lio/sentry/systemtest/util/RestTestClient; + public final fun getSentryClient ()Lio/sentry/systemtest/util/SentryMockServerClient; + public final fun logObject (Ljava/lang/Object;)V + public final fun reset ()V + public final fun setEnvelopeCounts (Lio/sentry/systemtest/util/EnvelopeCounts;)V + public final fun snapshotEnvelopeCount ()V +} + diff --git a/sentry-system-test-support/build.gradle.kts b/sentry-system-test-support/build.gradle.kts new file mode 100644 index 00000000000..b5392694a49 --- /dev/null +++ b/sentry-system-test-support/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + `java-library` + kotlin("jvm") + jacoco + id(Config.QualityPlugins.errorProne) + id(Config.QualityPlugins.gradleVersions) + id("com.apollographql.apollo3") version "3.8.2" +} + +configure { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +tasks.withType().configureEach { + kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString() +} + +dependencies { + api(projects.sentry) + compileOnly(Config.Libs.springBoot3StarterTest) { + exclude(group = "org.junit.vintage", module = "junit-vintage-engine") + } + compileOnly(Config.Libs.springBoot3StarterWeb) + api(Config.Libs.apolloKotlin) + implementation(Config.Libs.jacksonKotlin) + api(projects.sentryTestSupport) + + compileOnly(Config.CompileOnly.nopen) + errorprone(Config.CompileOnly.nopenChecker) + errorprone(Config.CompileOnly.errorprone) + compileOnly(Config.CompileOnly.jetbrainsAnnotations) + + // tests + implementation(kotlin(Config.kotlinStdLib)) + implementation(Config.TestLibs.kotlinTestJunit) + implementation(Config.TestLibs.mockitoKotlin) +} + +configure { + test { + java.srcDir("src/test/java") + } +} + +apollo { + service("service") { + srcDir("src/main/graphql") + packageName.set("io.sentry.samples.graphql") + outputDirConnection { + connectToKotlinSourceSet("main") + } + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/graphql/greeting.graphql b/sentry-system-test-support/src/main/graphql/greeting.graphql similarity index 100% rename from sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/graphql/greeting.graphql rename to sentry-system-test-support/src/main/graphql/greeting.graphql diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/graphql/project.graphql b/sentry-system-test-support/src/main/graphql/project.graphql similarity index 100% rename from sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/graphql/project.graphql rename to sentry-system-test-support/src/main/graphql/project.graphql diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/graphql/task.graphql b/sentry-system-test-support/src/main/graphql/task.graphql similarity index 100% rename from sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/graphql/task.graphql rename to sentry-system-test-support/src/main/graphql/task.graphql diff --git a/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/ResponseTypes.kt b/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/ResponseTypes.kt new file mode 100644 index 00000000000..fb7721bd942 --- /dev/null +++ b/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/ResponseTypes.kt @@ -0,0 +1,10 @@ +package io.sentry.systemtest + +data class Todo(val id: Long, val title: String, val isCompleted: Boolean) + +data class Person(val firstName: String, val lastName: String) { + + override fun toString(): String { + return "Person{firstName='$firstName', lastName='$lastName'}" + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt b/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt similarity index 100% rename from sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt rename to sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/graphql/GraphqlTestClient.kt diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt b/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt similarity index 100% rename from sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt rename to sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/LoggingInsecureRestClient.kt diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt b/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/RestTestClient.kt similarity index 51% rename from sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt rename to sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/RestTestClient.kt index f50632f381c..7a8792a13bf 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/RestTestClient.kt +++ b/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/RestTestClient.kt @@ -1,75 +1,67 @@ package io.sentry.systemtest.util -import io.sentry.samples.spring.boot.jakarta.Person -import io.sentry.samples.spring.boot.jakarta.Todo +import io.sentry.systemtest.Person +import io.sentry.systemtest.Todo import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod -import org.springframework.http.HttpStatusCode +import org.springframework.http.ResponseEntity import org.springframework.web.client.HttpStatusCodeException class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestClient() { - var lastKnownStatusCode: HttpStatusCode? = null + var lastKnownStatusCode: Int? = null fun getPerson(id: Long): Person? { return try { val response = restTemplate().exchange("$backendBaseUrl/person/{id}", HttpMethod.GET, entityWithAuth(), Person::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode + lastKnownStatusCode = statusCode(response) response.body } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode + lastKnownStatusCode = statusCode(e) null } } - fun createPerson(person: Person): Person? { + fun createPerson(person: Person, extraHeaders: Map? = null): Person? { return try { - val response = restTemplate().exchange("$backendBaseUrl/person/", HttpMethod.POST, entityWithAuth(person), Person::class.java, person) - lastKnownStatusCode = response.statusCode + val response = restTemplate().exchange("$backendBaseUrl/person/", HttpMethod.POST, entityWithAuth(person, extraHeaders), Person::class.java, person) + lastKnownStatusCode = statusCode(response) response.body } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode + lastKnownStatusCode = statusCode(e) null } } - fun getPersonDistributedTracing(id: Long, sentryTraceHeader: String? = null, baggageHeader: String? = null): Person? { + fun getPersonDistributedTracing(id: Long, extraHeaders: Map? = null): Person? { return try { - val response = restTemplate().exchange("$backendBaseUrl/tracing/{id}", HttpMethod.GET, entityWithAuth(headerCallback = tracingHeaders(sentryTraceHeader, baggageHeader)), Person::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode + val response = restTemplate().exchange("$backendBaseUrl/tracing/{id}", HttpMethod.GET, entityWithAuth(extraHeaders = extraHeaders), Person::class.java, mapOf("id" to id)) + lastKnownStatusCode = statusCode(response) response.body } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode + lastKnownStatusCode = statusCode(e) null } } - fun createPersonDistributedTracing(person: Person, sentryTraceHeader: String? = null, baggageHeader: String? = null): Person? { + fun createPersonDistributedTracing(person: Person, extraHeaders: Map? = null): Person? { return try { - val response = restTemplate().exchange("$backendBaseUrl/tracing/", HttpMethod.POST, entityWithAuth(person, tracingHeaders(sentryTraceHeader, baggageHeader)), Person::class.java, person) - lastKnownStatusCode = response.statusCode + val response = restTemplate().exchange("$backendBaseUrl/tracing/", HttpMethod.POST, entityWithAuth(person, extraHeaders), Person::class.java, person) + lastKnownStatusCode = statusCode(response) response.body } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode + lastKnownStatusCode = statusCode(e) null } } - private fun tracingHeaders(sentryTraceHeader: String?, baggageHeader: String?): (HttpHeaders) -> HttpHeaders { - return { httpHeaders -> - sentryTraceHeader?.let { httpHeaders.set("sentry-trace", it) } - baggageHeader?.let { httpHeaders.set("baggage", it) } - httpHeaders - } - } - fun getTodo(id: Long): Todo? { return try { val response = restTemplate().exchange("$backendBaseUrl/todo/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode + lastKnownStatusCode = statusCode(response) response.body } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode + lastKnownStatusCode = statusCode(e) null } } @@ -77,10 +69,10 @@ class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestCl fun getTodoWebclient(id: Long): Todo? { return try { val response = restTemplate().exchange("$backendBaseUrl/todo-webclient/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode + lastKnownStatusCode = statusCode(response) response.body } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode + lastKnownStatusCode = statusCode(e) null } } @@ -88,21 +80,34 @@ class RestTestClient(private val backendBaseUrl: String) : LoggingInsecureRestCl fun getTodoRestClient(id: Long): Todo? { return try { val response = restTemplate().exchange("$backendBaseUrl/todo-restclient/{id}", HttpMethod.GET, entityWithAuth(), Todo::class.java, mapOf("id" to id)) - lastKnownStatusCode = response.statusCode + lastKnownStatusCode = statusCode(response) response.body } catch (e: HttpStatusCodeException) { - lastKnownStatusCode = e.statusCode + lastKnownStatusCode = statusCode(e) null } } - private fun entityWithAuth(request: Any? = null, headerCallback: ((HttpHeaders) -> HttpHeaders)? = null): HttpEntity { + private fun entityWithAuth(request: Any? = null, extraHeaders: Map? = null): HttpEntity { val headers = HttpHeaders().also { it.setBasicAuth("user", "password") } + extraHeaders?.forEach { key, value -> headers.set(key, value) } + + return HttpEntity(request, headers) + } + + private fun statusCode(o: Any): Int? { + val statusCodeValue = (o as? ResponseEntity)?.statusCodeValue + if (statusCodeValue != null) { + return statusCodeValue + } - val modifiedHeaders = headerCallback?.invoke(headers) ?: headers + val errorStatusCodeValue = (o as? HttpStatusCodeException)?.rawStatusCode + if (errorStatusCodeValue != null) { + return errorStatusCodeValue + } - return HttpEntity(request, modifiedHeaders) + return null } } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt b/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt similarity index 100% rename from sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt rename to sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/SentryMockServerClient.kt diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt b/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/TestHelper.kt similarity index 95% rename from sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt rename to sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/TestHelper.kt index 14bac5cd0ea..bbd42fd7415 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/util/TestHelper.kt +++ b/sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/TestHelper.kt @@ -200,4 +200,15 @@ class TestHelper(backendUrl: String) { return true } + + fun doesTransactionHaveOp(transaction: SentryTransaction, op: String): Boolean { + val matches = transaction.contexts.trace?.operation == op + if (!matches) { + println("Unable to find transaction with op $op:") + logObject(transaction) + return false + } + + return true + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index e5fdc079f79..4c642f1abd6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,6 +26,7 @@ include( "sentry-apollo", "sentry-apollo-3", "sentry-apollo-4", + "sentry-system-test-support", "sentry-test-support", "sentry-log4j2", "sentry-logback", From 8a31db132565d240c13f560cd45d7e00d8cadc97 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 6 Mar 2025 12:04:25 +0100 Subject: [PATCH 6/8] add schema --- .../src/main/graphql/schema.graphqls | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 sentry-system-test-support/src/main/graphql/schema.graphqls diff --git a/sentry-system-test-support/src/main/graphql/schema.graphqls b/sentry-system-test-support/src/main/graphql/schema.graphqls new file mode 100644 index 00000000000..d76aca4756a --- /dev/null +++ b/sentry-system-test-support/src/main/graphql/schema.graphqls @@ -0,0 +1,70 @@ +type Query { + greeting(name: String! = "Spring"): String! + project(slug: ID!): Project + tasks(projectSlug: ID!): [Task] +} + +type Mutation { + addProject(slug: ID!): String! +} + +type Subscription { + notifyNewTask(projectSlug: ID!): Task +} + +""" A Project in the Spring portfolio """ +type Project { + """ Unique string id used in URLs """ + slug: ID! + """ Project name """ + name: String + """ URL of the git repository """ + repositoryUrl: String! + """ Current support status """ + status: ProjectStatus! +} + +""" A task """ +type Task { + """ ID """ + id: String! + """ Name """ + name: String! + """ ID of the Assignee """ + assigneeId: String + """ Assignee """ + assignee: Assignee + """ ID of the Creator """ + creatorId: String + """ Creator """ + creator: Creator +} + +""" An Assignee """ +type Assignee { + """ ID """ + id: String! + """ Name """ + name: String! +} + +""" An Creator """ +type Creator { + """ ID """ + id: String! + """ Name """ + name: String! +} + +enum ProjectStatus { + """ Actively supported by the Spring team """ + ACTIVE + """ Supported by the community """ + COMMUNITY + """ Prototype, not officially supported yet """ + INCUBATING + """ Project being retired, in maintenance mode """ + ATTIC + """ End-Of-Lifed """ + EOL +} From a3be332b85a77a76f551d798cf0e3b0a5efc102c Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 6 Mar 2025 12:35:01 +0100 Subject: [PATCH 7/8] Add distributed tracing tests to more modules --- .../jakarta/DistributedTracingController.java | 49 +++++ .../DistributedTracingSystemTest.kt | 190 ++++++++++++++++++ .../jakarta/DistributedTracingController.java | 49 +++++ .../DistributedTracingSystemTest.kt | 190 ++++++++++++++++++ .../boot/DistributedTracingController.java | 56 ++++++ .../DistributedTracingSystemTest.kt | 190 ++++++++++++++++++ .../boot/DistributedTracingController.java | 56 ++++++ .../DistributedTracingSystemTest.kt | 190 ++++++++++++++++++ .../jakarta/DistributedTracingController.java | 52 +++++ .../DistributedTracingSystemTest.kt | 190 ++++++++++++++++++ .../boot/DistributedTracingController.java | 52 +++++ .../DistributedTracingSystemTest.kt | 190 ++++++++++++++++++ .../boot/DistributedTracingController.java | 56 ++++++ .../DistributedTracingSystemTest.kt | 190 ++++++++++++++++++ 14 files changed, 1700 insertions(+) create mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java create mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt create mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java create mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt create mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java create mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt create mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java create mode 100644 sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt create mode 100644 sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java create mode 100644 sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt create mode 100644 sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java create mode 100644 sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt create mode 100644 sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java create mode 100644 sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java new file mode 100644 index 00000000000..d67059abb68 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java @@ -0,0 +1,49 @@ +package io.sentry.samples.spring.boot.jakarta; + +import java.nio.charset.Charset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestClient; + +@RestController +@RequestMapping("/tracing/") +public class DistributedTracingController { + private static final Logger LOGGER = LoggerFactory.getLogger(DistributedTracingController.class); + private final RestClient restClient; + + public DistributedTracingController(RestClient restClient) { + this.restClient = restClient; + } + + @GetMapping("{id}") + Person person(@PathVariable Long id) { + return restClient + .get() + .uri("http://localhost:8080/person/{id}", id) + .header( + HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .retrieve() + .body(Person.class); + } + + @PostMapping + Person create(@RequestBody Person person) { + return restClient + .post() + .uri("http://localhost:8080/person/") + .body(person) + .header( + HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .retrieve() + .body(Person.class); + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt new file mode 100644 index 00000000000..aa707f8b6e4 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt @@ -0,0 +1,190 @@ +package io.sentry.systemtest + +import io.sentry.protocol.SentryId +import io.sentry.systemtest.util.TestHelper +import org.junit.Before +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class DistributedTracingSystemTest { + + lateinit var testHelper: TestHelper + + @Before + fun setup() { + testHelper = TestHelper("http://localhost:8080") + testHelper.reset() + } + + @Test + fun `get person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } + + @Test + fun `get person distributed tracing with sampled false`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-0", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=false,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" + } + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" + } + } + + @Test + fun `get person distributed tracing without sample_rand`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRand1: String? = null + var sampleRand2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand1 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand2 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + assertEquals(sampleRand1, sampleRand2) + } + + @Test + fun `get person distributed tracing updates sample_rate on deferred decision`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRate1: String? = null + var sampleRate2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate1 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate2 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + assertEquals(sampleRate1, sampleRate2) + assertNotEquals(sampleRate1, "0.5") + } + + @Test + fun `create person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + val person = Person("firstA", "lastB") + val returnedPerson = restClient.createPersonDistributedTracing( + person, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(200, restClient.lastKnownStatusCode) + + assertEquals(person.firstName, returnedPerson!!.firstName) + assertEquals(person.lastName, returnedPerson!!.lastName) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /tracing/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /person/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java new file mode 100644 index 00000000000..d67059abb68 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java @@ -0,0 +1,49 @@ +package io.sentry.samples.spring.boot.jakarta; + +import java.nio.charset.Charset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestClient; + +@RestController +@RequestMapping("/tracing/") +public class DistributedTracingController { + private static final Logger LOGGER = LoggerFactory.getLogger(DistributedTracingController.class); + private final RestClient restClient; + + public DistributedTracingController(RestClient restClient) { + this.restClient = restClient; + } + + @GetMapping("{id}") + Person person(@PathVariable Long id) { + return restClient + .get() + .uri("http://localhost:8080/person/{id}", id) + .header( + HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .retrieve() + .body(Person.class); + } + + @PostMapping + Person create(@RequestBody Person person) { + return restClient + .post() + .uri("http://localhost:8080/person/") + .body(person) + .header( + HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .retrieve() + .body(Person.class); + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt new file mode 100644 index 00000000000..aa707f8b6e4 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt @@ -0,0 +1,190 @@ +package io.sentry.systemtest + +import io.sentry.protocol.SentryId +import io.sentry.systemtest.util.TestHelper +import org.junit.Before +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class DistributedTracingSystemTest { + + lateinit var testHelper: TestHelper + + @Before + fun setup() { + testHelper = TestHelper("http://localhost:8080") + testHelper.reset() + } + + @Test + fun `get person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } + + @Test + fun `get person distributed tracing with sampled false`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-0", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=false,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" + } + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" + } + } + + @Test + fun `get person distributed tracing without sample_rand`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRand1: String? = null + var sampleRand2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand1 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand2 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + assertEquals(sampleRand1, sampleRand2) + } + + @Test + fun `get person distributed tracing updates sample_rate on deferred decision`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRate1: String? = null + var sampleRate2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate1 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate2 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + assertEquals(sampleRate1, sampleRate2) + assertNotEquals(sampleRate1, "0.5") + } + + @Test + fun `create person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + val person = Person("firstA", "lastB") + val returnedPerson = restClient.createPersonDistributedTracing( + person, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(200, restClient.lastKnownStatusCode) + + assertEquals(person.firstName, returnedPerson!!.firstName) + assertEquals(person.lastName, returnedPerson!!.lastName) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /tracing/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /person/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java new file mode 100644 index 00000000000..5fe91518cf8 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java @@ -0,0 +1,56 @@ +package io.sentry.samples.spring.boot; + +import java.nio.charset.Charset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +@RestController +@RequestMapping("/tracing/") +public class DistributedTracingController { + private static final Logger LOGGER = LoggerFactory.getLogger(DistributedTracingController.class); + private final RestTemplate restTemplate; + + public DistributedTracingController(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @GetMapping("{id}") + Person person(@PathVariable Long id) { + return restTemplate + .exchange( + "http://localhost:8080/person/" + id, + HttpMethod.GET, + new HttpEntity(createHeaders()), + Person.class) + .getBody(); + } + + @PostMapping + Person create(@RequestBody Person person) { + return restTemplate + .exchange( + "http://localhost:8080/person/", + HttpMethod.POST, + new HttpEntity(person, createHeaders()), + Person.class) + .getBody(); + } + + private HttpHeaders createHeaders() { + HttpHeaders headers = new HttpHeaders(); + + headers.setBasicAuth("user", "password", Charset.defaultCharset()); + + return headers; + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt new file mode 100644 index 00000000000..aa707f8b6e4 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt @@ -0,0 +1,190 @@ +package io.sentry.systemtest + +import io.sentry.protocol.SentryId +import io.sentry.systemtest.util.TestHelper +import org.junit.Before +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class DistributedTracingSystemTest { + + lateinit var testHelper: TestHelper + + @Before + fun setup() { + testHelper = TestHelper("http://localhost:8080") + testHelper.reset() + } + + @Test + fun `get person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } + + @Test + fun `get person distributed tracing with sampled false`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-0", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=false,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" + } + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" + } + } + + @Test + fun `get person distributed tracing without sample_rand`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRand1: String? = null + var sampleRand2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand1 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand2 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + assertEquals(sampleRand1, sampleRand2) + } + + @Test + fun `get person distributed tracing updates sample_rate on deferred decision`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRate1: String? = null + var sampleRate2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate1 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate2 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + assertEquals(sampleRate1, sampleRate2) + assertNotEquals(sampleRate1, "0.5") + } + + @Test + fun `create person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + val person = Person("firstA", "lastB") + val returnedPerson = restClient.createPersonDistributedTracing( + person, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(200, restClient.lastKnownStatusCode) + + assertEquals(person.firstName, returnedPerson!!.firstName) + assertEquals(person.lastName, returnedPerson!!.lastName) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /tracing/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /person/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java new file mode 100644 index 00000000000..5fe91518cf8 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java @@ -0,0 +1,56 @@ +package io.sentry.samples.spring.boot; + +import java.nio.charset.Charset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +@RestController +@RequestMapping("/tracing/") +public class DistributedTracingController { + private static final Logger LOGGER = LoggerFactory.getLogger(DistributedTracingController.class); + private final RestTemplate restTemplate; + + public DistributedTracingController(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @GetMapping("{id}") + Person person(@PathVariable Long id) { + return restTemplate + .exchange( + "http://localhost:8080/person/" + id, + HttpMethod.GET, + new HttpEntity(createHeaders()), + Person.class) + .getBody(); + } + + @PostMapping + Person create(@RequestBody Person person) { + return restTemplate + .exchange( + "http://localhost:8080/person/", + HttpMethod.POST, + new HttpEntity(person, createHeaders()), + Person.class) + .getBody(); + } + + private HttpHeaders createHeaders() { + HttpHeaders headers = new HttpHeaders(); + + headers.setBasicAuth("user", "password", Charset.defaultCharset()); + + return headers; + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt new file mode 100644 index 00000000000..aa707f8b6e4 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt @@ -0,0 +1,190 @@ +package io.sentry.systemtest + +import io.sentry.protocol.SentryId +import io.sentry.systemtest.util.TestHelper +import org.junit.Before +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class DistributedTracingSystemTest { + + lateinit var testHelper: TestHelper + + @Before + fun setup() { + testHelper = TestHelper("http://localhost:8080") + testHelper.reset() + } + + @Test + fun `get person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } + + @Test + fun `get person distributed tracing with sampled false`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-0", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=false,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" + } + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" + } + } + + @Test + fun `get person distributed tracing without sample_rand`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRand1: String? = null + var sampleRand2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand1 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand2 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + assertEquals(sampleRand1, sampleRand2) + } + + @Test + fun `get person distributed tracing updates sample_rate on deferred decision`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRate1: String? = null + var sampleRate2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate1 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate2 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + assertEquals(sampleRate1, sampleRate2) + assertNotEquals(sampleRate1, "0.5") + } + + @Test + fun `create person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + val person = Person("firstA", "lastB") + val returnedPerson = restClient.createPersonDistributedTracing( + person, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(200, restClient.lastKnownStatusCode) + + assertEquals(person.firstName, returnedPerson!!.firstName) + assertEquals(person.lastName, returnedPerson!!.lastName) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /tracing/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /person/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java new file mode 100644 index 00000000000..0b2360ee8da --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java @@ -0,0 +1,52 @@ +package io.sentry.samples.spring.boot.jakarta; + +import java.nio.charset.Charset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/tracing/") +public class DistributedTracingController { + private static final Logger LOGGER = LoggerFactory.getLogger(DistributedTracingController.class); + private final WebClient webClient; + + public DistributedTracingController(WebClient webClient) { + this.webClient = webClient; + } + + @GetMapping("{id}") + Mono person(@PathVariable Long id) { + return webClient + .get() + .uri("http://localhost:8080/person/{id}", id) + .header( + HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .retrieve() + .bodyToMono(Person.class) + .map(response -> response); + } + + @PostMapping + Mono create(@RequestBody Person person) { + return webClient + .post() + .uri("http://localhost:8080/person/") + .header( + HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .body(person, Person.class) + .retrieve() + .bodyToMono(Person.class) + .map(response -> response); + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt new file mode 100644 index 00000000000..aa707f8b6e4 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt @@ -0,0 +1,190 @@ +package io.sentry.systemtest + +import io.sentry.protocol.SentryId +import io.sentry.systemtest.util.TestHelper +import org.junit.Before +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class DistributedTracingSystemTest { + + lateinit var testHelper: TestHelper + + @Before + fun setup() { + testHelper = TestHelper("http://localhost:8080") + testHelper.reset() + } + + @Test + fun `get person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } + + @Test + fun `get person distributed tracing with sampled false`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-0", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=false,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" + } + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" + } + } + + @Test + fun `get person distributed tracing without sample_rand`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRand1: String? = null + var sampleRand2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand1 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand2 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + assertEquals(sampleRand1, sampleRand2) + } + + @Test + fun `get person distributed tracing updates sample_rate on deferred decision`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRate1: String? = null + var sampleRate2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate1 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate2 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + assertEquals(sampleRate1, sampleRate2) + assertNotEquals(sampleRate1, "0.5") + } + + @Test + fun `create person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + val person = Person("firstA", "lastB") + val returnedPerson = restClient.createPersonDistributedTracing( + person, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(200, restClient.lastKnownStatusCode) + + assertEquals(person.firstName, returnedPerson!!.firstName) + assertEquals(person.lastName, returnedPerson!!.lastName) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /tracing/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /person/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java new file mode 100644 index 00000000000..786503b6c6c --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java @@ -0,0 +1,52 @@ +package io.sentry.samples.spring.boot; + +import java.nio.charset.Charset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/tracing/") +public class DistributedTracingController { + private static final Logger LOGGER = LoggerFactory.getLogger(DistributedTracingController.class); + private final WebClient webClient; + + public DistributedTracingController(WebClient webClient) { + this.webClient = webClient; + } + + @GetMapping("{id}") + Mono person(@PathVariable Long id) { + return webClient + .get() + .uri("http://localhost:8080/person/{id}", id) + .header( + HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .retrieve() + .bodyToMono(Person.class) + .map(response -> response); + } + + @PostMapping + Mono create(@RequestBody Person person) { + return webClient + .post() + .uri("http://localhost:8080/person/") + .header( + HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .body(person, Person.class) + .retrieve() + .bodyToMono(Person.class) + .map(response -> response); + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt new file mode 100644 index 00000000000..aa707f8b6e4 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt @@ -0,0 +1,190 @@ +package io.sentry.systemtest + +import io.sentry.protocol.SentryId +import io.sentry.systemtest.util.TestHelper +import org.junit.Before +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class DistributedTracingSystemTest { + + lateinit var testHelper: TestHelper + + @Before + fun setup() { + testHelper = TestHelper("http://localhost:8080") + testHelper.reset() + } + + @Test + fun `get person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } + + @Test + fun `get person distributed tracing with sampled false`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-0", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=false,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" + } + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" + } + } + + @Test + fun `get person distributed tracing without sample_rand`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRand1: String? = null + var sampleRand2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand1 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand2 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + assertEquals(sampleRand1, sampleRand2) + } + + @Test + fun `get person distributed tracing updates sample_rate on deferred decision`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRate1: String? = null + var sampleRate2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate1 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate2 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + assertEquals(sampleRate1, sampleRate2) + assertNotEquals(sampleRate1, "0.5") + } + + @Test + fun `create person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + val person = Person("firstA", "lastB") + val returnedPerson = restClient.createPersonDistributedTracing( + person, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(200, restClient.lastKnownStatusCode) + + assertEquals(person.firstName, returnedPerson!!.firstName) + assertEquals(person.lastName, returnedPerson!!.lastName) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /tracing/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /person/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } +} diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java new file mode 100644 index 00000000000..5fe91518cf8 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java @@ -0,0 +1,56 @@ +package io.sentry.samples.spring.boot; + +import java.nio.charset.Charset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +@RestController +@RequestMapping("/tracing/") +public class DistributedTracingController { + private static final Logger LOGGER = LoggerFactory.getLogger(DistributedTracingController.class); + private final RestTemplate restTemplate; + + public DistributedTracingController(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @GetMapping("{id}") + Person person(@PathVariable Long id) { + return restTemplate + .exchange( + "http://localhost:8080/person/" + id, + HttpMethod.GET, + new HttpEntity(createHeaders()), + Person.class) + .getBody(); + } + + @PostMapping + Person create(@RequestBody Person person) { + return restTemplate + .exchange( + "http://localhost:8080/person/", + HttpMethod.POST, + new HttpEntity(person, createHeaders()), + Person.class) + .getBody(); + } + + private HttpHeaders createHeaders() { + HttpHeaders headers = new HttpHeaders(); + + headers.setBasicAuth("user", "password", Charset.defaultCharset()); + + return headers; + } +} diff --git a/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt new file mode 100644 index 00000000000..aa707f8b6e4 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/DistributedTracingSystemTest.kt @@ -0,0 +1,190 @@ +package io.sentry.systemtest + +import io.sentry.protocol.SentryId +import io.sentry.systemtest.util.TestHelper +import org.junit.Before +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class DistributedTracingSystemTest { + + lateinit var testHelper: TestHelper + + @Before + fun setup() { + testHelper = TestHelper("http://localhost:8080") + testHelper.reset() + } + + @Test + fun `get person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } + + @Test + fun `get person distributed tracing with sampled false`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-0", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=false,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /tracing/{id}" + } + + testHelper.ensureNoTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "GET /person/{id}" + } + } + + @Test + fun `get person distributed tracing without sample_rand`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRand1: String? = null + var sampleRand2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand1 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRand2 = envelopeHeader.traceContext?.sampleRand + } + + matches + } + + assertEquals(sampleRand1, sampleRand2) + } + + @Test + fun `get person distributed tracing updates sample_rate on deferred decision`() { + val traceId = SentryId() + val restClient = testHelper.restClient + restClient.getPersonDistributedTracing( + 1L, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.5,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(500, restClient.lastKnownStatusCode) + + var sampleRate1: String? = null + var sampleRate2: String? = null + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + + val matches = transaction.transaction == "GET /tracing/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate1 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + val matches = transaction.transaction == "GET /person/{id}" && + envelopeHeader.traceContext!!.traceId == traceId && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + + if (matches) { + testHelper.logObject(envelopeHeader) + testHelper.logObject(transaction) + sampleRate2 = envelopeHeader.traceContext?.sampleRate + } + + matches + } + + assertEquals(sampleRate1, sampleRate2) + assertNotEquals(sampleRate1, "0.5") + } + + @Test + fun `create person distributed tracing`() { + val traceId = SentryId() + val restClient = testHelper.restClient + val person = Person("firstA", "lastB") + val returnedPerson = restClient.createPersonDistributedTracing( + person, + mapOf( + "sentry-trace" to "$traceId-424cffc8f94feeee-1", + "baggage" to "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=HTTP%20GET" + ) + ) + assertEquals(200, restClient.lastKnownStatusCode) + + assertEquals(person.firstName, returnedPerson!!.firstName) + assertEquals(person.lastName, returnedPerson!!.lastName) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /tracing/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + transaction.transaction == "POST /person/" && + testHelper.doesTransactionHaveTraceId(transaction, traceId.toString()) + } + } +} From 3c4b712bd755e3d6dd9b312603017ef7c68ac503 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 6 Mar 2025 13:17:31 +0100 Subject: [PATCH 8/8] use mono.just for post body --- .../spring/boot/jakarta/DistributedTracingController.java | 2 +- .../samples/spring/boot/DistributedTracingController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java index 0b2360ee8da..38409509905 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/DistributedTracingController.java @@ -44,7 +44,7 @@ Mono create(@RequestBody Person person) { .header( HttpHeaders.AUTHORIZATION, "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) - .body(person, Person.class) + .body(Mono.just(person), Person.class) .retrieve() .bodyToMono(Person.class) .map(response -> response); diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java index 786503b6c6c..cd69d854006 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java @@ -44,7 +44,7 @@ Mono create(@RequestBody Person person) { .header( HttpHeaders.AUTHORIZATION, "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) - .body(person, Person.class) + .body(Mono.just(person), Person.class) .retrieve() .bodyToMono(Person.class) .map(response -> response);