@@ -3,9 +3,12 @@ package io.sentry.spring.jakarta.kafka
33import kotlin.test.Test
44import kotlin.test.assertSame
55import kotlin.test.assertTrue
6+ import org.apache.kafka.clients.consumer.Consumer
7+ import org.apache.kafka.clients.consumer.ConsumerRecord
68import org.mockito.kotlin.mock
79import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory
810import org.springframework.kafka.core.ConsumerFactory
11+ import org.springframework.kafka.listener.RecordInterceptor
912
1013class SentryKafkaConsumerBeanPostProcessorTest {
1114
@@ -55,4 +58,67 @@ class SentryKafkaConsumerBeanPostProcessorTest {
5558
5659 assertSame(someBean, result)
5760 }
61+
62+ @Test
63+ fun `chains existing customer RecordInterceptor as delegate` () {
64+ val consumerFactory = mock<ConsumerFactory <String , String >>()
65+ val factory = ConcurrentKafkaListenerContainerFactory <String , String >()
66+ factory.consumerFactory = consumerFactory
67+
68+ val customerInterceptor =
69+ object : RecordInterceptor <String , String > {
70+ override fun intercept (
71+ record : ConsumerRecord <String , String >,
72+ consumer : Consumer <String , String >,
73+ ): ConsumerRecord <String , String >? = record
74+ }
75+ factory.setRecordInterceptor(customerInterceptor)
76+
77+ val processor = SentryKafkaConsumerBeanPostProcessor ()
78+ processor.postProcessAfterInitialization(factory, " kafkaListenerContainerFactory" )
79+
80+ val field = factory.javaClass.superclass.getDeclaredField(" recordInterceptor" )
81+ field.isAccessible = true
82+ val installed = field.get(factory)
83+ assertTrue(
84+ installed is SentryKafkaRecordInterceptor <* , * >,
85+ " expected SentryKafkaRecordInterceptor, got ${installed?.javaClass} " ,
86+ )
87+
88+ val delegateField = SentryKafkaRecordInterceptor ::class .java.getDeclaredField(" delegate" )
89+ delegateField.isAccessible = true
90+ assertSame(
91+ customerInterceptor,
92+ delegateField.get(installed),
93+ " customer interceptor must be preserved as delegate" ,
94+ )
95+ }
96+
97+ @Test
98+ fun `skips installation when reflection fails and preserves customer interceptor` () {
99+ val consumerFactory = mock<ConsumerFactory <String , String >>()
100+ val factory = ConcurrentKafkaListenerContainerFactory <String , String >()
101+ factory.consumerFactory = consumerFactory
102+ val customerInterceptor =
103+ object : RecordInterceptor <String , String > {
104+ override fun intercept (
105+ record : ConsumerRecord <String , String >,
106+ consumer : Consumer <String , String >,
107+ ): ConsumerRecord <String , String >? = record
108+ }
109+ factory.setRecordInterceptor(customerInterceptor)
110+
111+ val field = factory.javaClass.superclass.getDeclaredField(" recordInterceptor" )
112+ field.isAccessible = true
113+ assertSame(customerInterceptor, field.get(factory))
114+
115+ val processor = SentryKafkaConsumerBeanPostProcessor (" missingRecordInterceptor" )
116+ processor.postProcessAfterInitialization(factory, " kafkaListenerContainerFactory" )
117+
118+ assertSame(
119+ customerInterceptor,
120+ field.get(factory),
121+ " customer interceptor must remain installed when Sentry cannot read it" ,
122+ )
123+ }
58124}
0 commit comments