From db43a0748149dd3085bba592c7e1778ee25a4c4b Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 20 Oct 2026 19:44:22 +0200 Subject: [PATCH 1/2] test(core): Add an end-to-end test of the core APIs over the HTTP transport SentryEndToEndTest reports through the APIs an app uses without integrations and checks what reaches MockSentryServer: messages at the scope's and an explicit level, an exception with its feature flag, an event with a fingerprint, breadcrumbs from an event processor and a beforeBreadcrumb callback, ten events in a row, a transaction with a child span, and metrics with the scope's attributes. It ports the core part of the console sample's system test, which ran the sample's JAR against the Python mock server. The sample's profiling and Kafka parts follow in their own modules. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../test/java/io/sentry/SentryEndToEndTest.kt | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 sentry/src/test/java/io/sentry/SentryEndToEndTest.kt diff --git a/sentry/src/test/java/io/sentry/SentryEndToEndTest.kt b/sentry/src/test/java/io/sentry/SentryEndToEndTest.kt new file mode 100644 index 0000000000..1406231209 --- /dev/null +++ b/sentry/src/test/java/io/sentry/SentryEndToEndTest.kt @@ -0,0 +1,111 @@ +package io.sentry + +import com.google.common.truth.Truth.assertThat +import io.sentry.protocol.Message +import io.sentry.test.MockSentryServer +import io.sentry.test.event +import io.sentry.test.hasFlag +import io.sentry.test.hasMetric +import io.sentry.test.hasMetricWithAttribute +import io.sentry.test.initForTest +import io.sentry.test.metrics +import io.sentry.test.transaction +import kotlin.test.AfterTest +import kotlin.test.Test + +/** Reports through the APIs an app uses without integrations, over the real HTTP transport. */ +class SentryEndToEndTest { + private val server = MockSentryServer() + + @AfterTest + fun teardown() { + Sentry.close() + server.close() + } + + @Test + fun `events, a transaction and metrics reach Sentry`() { + initForTest { + it.dsn = server.dsn + it.tracesSampleRate = 1.0 + it.beforeBreadcrumb = SentryOptions.BeforeBreadcrumbCallback { breadcrumb, _ -> + if (breadcrumb.message == "bad breadcrumb") null else breadcrumb + } + } + Sentry.addBreadcrumb("bad breadcrumb") + Sentry.configureScope { + it.addEventProcessor( + object : EventProcessor { + override fun process(event: SentryEvent, hint: Hint): SentryEvent = event.apply { + if ((level?.ordinal ?: 0) > SentryLevel.INFO.ordinal) { + addBreadcrumb(Breadcrumb("Processed by the event processor")) + } + } + } + ) + } + + Sentry.withScope { + it.level = SentryLevel.FATAL + Sentry.captureMessage("Fatal message!") + } + Sentry.captureMessage("Some warning!", SentryLevel.WARNING) + Sentry.addFeatureFlag("my-feature-flag", true) + Sentry.setAttribute("user.type", "admin") + Sentry.setAttribute("feature.version", 2) + Sentry.metrics().count("countMetric") + Sentry.metrics().gauge("gaugeMetric", 5.0) + Sentry.metrics().distribution("distributionMetric", 7.0) + Sentry.captureException(RuntimeException("Some error!")) + Sentry.captureEvent( + SentryEvent().apply { + message = Message().apply { message = "Detailed event" } + fingerprints = listOf("NewClientDebug") + level = SentryLevel.DEBUG + } + ) + repeat(10) { i -> + Sentry.captureEvent( + SentryEvent().apply { message = Message().apply { message = "event $i" } } + ) + } + val transaction = Sentry.startTransaction("transaction name", "op") + transaction.startChild("child").finish() + transaction.finish() + Sentry.flush(5000) + + val fatal = + server.awaitEnvelope { it.event()?.message?.formatted == "Fatal message!" }.event()!! + assertThat(fatal.level).isEqualTo(SentryLevel.FATAL) + assertThat(fatal.breadcrumbs!!.map { it.message }).contains("Processed by the event processor") + assertThat(fatal.breadcrumbs!!.map { it.message }).doesNotContain("bad breadcrumb") + + val warning = + server.awaitEnvelope { it.event()?.message?.formatted == "Some warning!" }.event()!! + assertThat(warning.level).isEqualTo(SentryLevel.WARNING) + + val error = + server + .awaitEnvelope { it.event()?.exceptions?.any { e -> e.value == "Some error!" } == true } + .event()!! + assertThat(error.hasFlag("my-feature-flag", true)).isTrue() + + val detailed = + server.awaitEnvelope { it.event()?.message?.message == "Detailed event" }.event()!! + assertThat(detailed.fingerprints).containsExactly("NewClientDebug") + assertThat(detailed.level).isEqualTo(SentryLevel.DEBUG) + + repeat(10) { i -> server.awaitEnvelope { it.event()?.message?.message == "event $i" } } + + val sent = + server.awaitEnvelope { it.transaction()?.transaction == "transaction name" }.transaction()!! + assertThat(sent.spans.map { it.op }).contains("child") + + val metrics = server.awaitEnvelope { it.metrics() != null }.metrics()!! + assertThat(metrics.hasMetric("countMetric", "counter", 1.0)).isTrue() + assertThat(metrics.hasMetric("gaugeMetric", "gauge", 5.0)).isTrue() + assertThat(metrics.hasMetric("distributionMetric", "distribution", 7.0)).isTrue() + assertThat(metrics.hasMetricWithAttribute("countMetric", "user.type", "admin")).isTrue() + assertThat(metrics.hasMetricWithAttribute("countMetric", "feature.version", 2)).isTrue() + } +} From 67c6c50039588f328beadbdb0a5f460f7126cfee Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 20 Oct 2026 21:06:05 +0200 Subject: [PATCH 2/2] Merge the audit fixes from the branches below, and check the exception type end to end The console sample's test checked that the captured exception arrives with its type, RuntimeException, not just its value. Co-Authored-By: Claude Opus 5.5 (1M context) --- sentry/src/test/java/io/sentry/SentryEndToEndTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry/src/test/java/io/sentry/SentryEndToEndTest.kt b/sentry/src/test/java/io/sentry/SentryEndToEndTest.kt index 1406231209..742f4462b2 100644 --- a/sentry/src/test/java/io/sentry/SentryEndToEndTest.kt +++ b/sentry/src/test/java/io/sentry/SentryEndToEndTest.kt @@ -88,6 +88,8 @@ class SentryEndToEndTest { server .awaitEnvelope { it.event()?.exceptions?.any { e -> e.value == "Some error!" } == true } .event()!! + assertThat(error.exceptions!!.map { it.type to it.value }) + .contains("RuntimeException" to "Some error!") assertThat(error.hasFlag("my-feature-flag", true)).isTrue() val detailed =