@@ -7,13 +7,16 @@ import io.sentry.Sentry
77import io.sentry.SentryOptions
88import io.sentry.SentryTraceHeader
99import io.sentry.SentryTracer
10+ import io.sentry.SpanDataConvention
1011import io.sentry.TransactionContext
1112import io.sentry.test.initForTest
13+ import java.nio.ByteBuffer
1214import java.nio.charset.StandardCharsets
1315import kotlin.test.AfterTest
1416import kotlin.test.BeforeTest
1517import kotlin.test.Test
1618import kotlin.test.assertEquals
19+ import kotlin.test.assertNull
1720import org.apache.kafka.clients.consumer.Consumer
1821import org.apache.kafka.clients.consumer.ConsumerRecord
1922import org.apache.kafka.common.header.internals.RecordHeaders
@@ -24,6 +27,7 @@ import org.mockito.kotlin.never
2427import org.mockito.kotlin.verify
2528import org.mockito.kotlin.whenever
2629import org.springframework.kafka.listener.RecordInterceptor
30+ import org.springframework.kafka.support.KafkaHeaders
2731
2832class SentryKafkaRecordInterceptorTest {
2933
@@ -32,6 +36,7 @@ class SentryKafkaRecordInterceptorTest {
3236 private lateinit var options: SentryOptions
3337 private lateinit var consumer: Consumer <String , String >
3438 private lateinit var lifecycleToken: ISentryLifecycleToken
39+ private lateinit var transaction: SentryTracer
3540
3641 @BeforeTest
3742 fun setup () {
@@ -52,8 +57,9 @@ class SentryKafkaRecordInterceptorTest {
5257 whenever(forkedScopes.options).thenReturn(options)
5358 whenever(forkedScopes.makeCurrent()).thenReturn(lifecycleToken)
5459
55- val tx = SentryTracer (TransactionContext (" queue.process" , " queue.process" ), forkedScopes)
56- whenever(forkedScopes.startTransaction(any<TransactionContext >(), any())).thenReturn(tx)
60+ transaction = SentryTracer (TransactionContext (" queue.process" , " queue.process" ), forkedScopes)
61+ whenever(forkedScopes.startTransaction(any<TransactionContext >(), any()))
62+ .thenReturn(transaction)
5763 }
5864
5965 @AfterTest
@@ -81,6 +87,7 @@ class SentryKafkaRecordInterceptorTest {
8187 sentryTrace : String? = null,
8288 baggage : String? = null,
8389 enqueuedTime : Long? = null,
90+ deliveryAttempt : Int? = null,
8491 ): ConsumerRecord <String , String > {
8592 val headers = RecordHeaders ()
8693 sentryTrace?.let {
@@ -95,6 +102,12 @@ class SentryKafkaRecordInterceptorTest {
95102 it.toString().toByteArray(StandardCharsets .UTF_8 ),
96103 )
97104 }
105+ deliveryAttempt?.let {
106+ headers.add(
107+ KafkaHeaders .DELIVERY_ATTEMPT ,
108+ ByteBuffer .allocate(Int .SIZE_BYTES ).putInt(it).array(),
109+ )
110+ }
98111 val record = ConsumerRecord <String , String >(" my-topic" , 0 , 0L , " key" , " value" )
99112 headers.forEach { record.headers().add(it) }
100113 return record
@@ -132,6 +145,26 @@ class SentryKafkaRecordInterceptorTest {
132145 verify(forkedScopes).continueTrace(org.mockito.kotlin.isNull(), org.mockito.kotlin.isNull())
133146 }
134147
148+ @Test
149+ fun `sets retry count from delivery attempt header` () {
150+ val interceptor = SentryKafkaRecordInterceptor <String , String >(scopes)
151+ val record = createRecordWithHeaders(deliveryAttempt = 3 )
152+
153+ withMockSentry { interceptor.intercept(record, consumer) }
154+
155+ assertEquals(2 , transaction.data?.get(SpanDataConvention .MESSAGING_MESSAGE_RETRY_COUNT ))
156+ }
157+
158+ @Test
159+ fun `does not set retry count when delivery attempt header is missing` () {
160+ val interceptor = SentryKafkaRecordInterceptor <String , String >(scopes)
161+ val record = createRecord()
162+
163+ withMockSentry { interceptor.intercept(record, consumer) }
164+
165+ assertNull(transaction.data?.get(SpanDataConvention .MESSAGING_MESSAGE_RETRY_COUNT ))
166+ }
167+
135168 @Test
136169 fun `does not create span when queue tracing is disabled` () {
137170 options.isEnableQueueTracing = false
0 commit comments