1717package org .apache .kafka .common .requests ;
1818
1919import org .apache .kafka .common .TopicPartition ;
20+ import org .apache .kafka .common .message .WriteTxnMarkersRequestData ;
2021import org .apache .kafka .common .protocol .ApiKeys ;
22+ import org .apache .kafka .common .protocol .ByteBufferAccessor ;
2123import org .apache .kafka .common .protocol .Errors ;
2224
2325import org .junit .jupiter .api .BeforeEach ;
2729import java .util .List ;
2830
2931import static org .junit .jupiter .api .Assertions .assertEquals ;
32+ import static org .junit .jupiter .api .Assertions .assertNotNull ;
3033
3134public class WriteTxnMarkersRequestTest {
3235
@@ -45,23 +48,51 @@ public void setUp() {
4548 markers = Collections .singletonList (
4649 new WriteTxnMarkersRequest .TxnMarkerEntry (
4750 PRODUCER_ID , PRODUCER_EPOCH , COORDINATOR_EPOCH ,
48- RESULT , Collections .singletonList (TOPIC_PARTITION ))
51+ RESULT , Collections .singletonList (TOPIC_PARTITION ), ( short ) 0 )
4952 );
5053 }
5154
5255 @ Test
5356 public void testConstructor () {
54- WriteTxnMarkersRequest .Builder builder = new WriteTxnMarkersRequest .Builder (markers );
55- for (short version : ApiKeys .WRITE_TXN_MARKERS .allVersions ()) {
56- WriteTxnMarkersRequest request = builder .build (version );
57- assertEquals (1 , request .markers ().size ());
58- WriteTxnMarkersRequest .TxnMarkerEntry marker = request .markers ().get (0 );
59- assertEquals (PRODUCER_ID , marker .producerId ());
60- assertEquals (PRODUCER_EPOCH , marker .producerEpoch ());
61- assertEquals (COORDINATOR_EPOCH , marker .coordinatorEpoch ());
62- assertEquals (RESULT , marker .transactionResult ());
63- assertEquals (Collections .singletonList (TOPIC_PARTITION ), marker .partitions ());
64- }
57+ // We always set the transaction version in the request data using the arguments provided to the builder.
58+ // If the version doesn't support it, it will be omitted during serialization.
59+
60+ // Test constructor with transactionVersion = 2
61+ List <WriteTxnMarkersRequest .TxnMarkerEntry > markersWithVersion = Collections .singletonList (
62+ new WriteTxnMarkersRequest .TxnMarkerEntry (
63+ PRODUCER_ID , PRODUCER_EPOCH , COORDINATOR_EPOCH ,
64+ RESULT , Collections .singletonList (TOPIC_PARTITION ), (short ) 2 )
65+ );
66+
67+ // Build with request version 1.
68+ WriteTxnMarkersRequest .Builder builder = new WriteTxnMarkersRequest .Builder (markersWithVersion );
69+ WriteTxnMarkersRequest request = builder .build ((short ) 1 );
70+ assertEquals (1 , request .data ().markers ().size ());
71+ WriteTxnMarkersRequestData .WritableTxnMarker dataMarker = request .data ().markers ().get (0 );
72+ assertEquals (PRODUCER_ID , dataMarker .producerId ());
73+ assertEquals (PRODUCER_EPOCH , dataMarker .producerEpoch ());
74+ assertEquals (COORDINATOR_EPOCH , dataMarker .coordinatorEpoch ());
75+ assertEquals (RESULT .id , dataMarker .transactionResult ());
76+ assertEquals (1 , dataMarker .topics ().size ());
77+ assertEquals (TOPIC_PARTITION .topic (), dataMarker .topics ().get (0 ).name ());
78+ assertEquals (Collections .singletonList (TOPIC_PARTITION .partition ()), dataMarker .topics ().get (0 ).partitionIndexes ());
79+ // Verify TransactionVersion is set to 2 in the data irrespective of the request version
80+ assertEquals ((byte ) 2 , dataMarker .transactionVersion ());
81+
82+ // Build with request version 2
83+ WriteTxnMarkersRequest .Builder builderWithVersions = new WriteTxnMarkersRequest .Builder (markersWithVersion );
84+ WriteTxnMarkersRequest requestWithVersion = builderWithVersions .build ((short ) 2 );
85+ assertEquals (1 , requestWithVersion .data ().markers ().size ());
86+ WriteTxnMarkersRequestData .WritableTxnMarker dataMarkerWithVersion = requestWithVersion .data ().markers ().get (0 );
87+ assertEquals (PRODUCER_ID , dataMarkerWithVersion .producerId ());
88+ assertEquals (PRODUCER_EPOCH , dataMarkerWithVersion .producerEpoch ());
89+ assertEquals (COORDINATOR_EPOCH , dataMarkerWithVersion .coordinatorEpoch ());
90+ assertEquals (RESULT .id , dataMarkerWithVersion .transactionResult ());
91+ assertEquals (1 , dataMarkerWithVersion .topics ().size ());
92+ assertEquals (TOPIC_PARTITION .topic (), dataMarkerWithVersion .topics ().get (0 ).name ());
93+ assertEquals (Collections .singletonList (TOPIC_PARTITION .partition ()), dataMarkerWithVersion .topics ().get (0 ).partitionIndexes ());
94+ // Verify TransactionVersion is set to 2 in the data
95+ assertEquals ((byte ) 2 , dataMarkerWithVersion .transactionVersion ());
6596 }
6697
6798 @ Test
@@ -79,4 +110,110 @@ public void testGetErrorResponse() {
79110 assertEquals (0 , errorResponse .throttleTimeMs ());
80111 }
81112 }
113+
114+ @ Test
115+ public void testTransactionVersion () {
116+ // Test that TransactionVersion is set correctly and serialization handles it properly.
117+ List <WriteTxnMarkersRequest .TxnMarkerEntry > markersWithVersion = Collections .singletonList (
118+ new WriteTxnMarkersRequest .TxnMarkerEntry (
119+ PRODUCER_ID , PRODUCER_EPOCH , COORDINATOR_EPOCH ,
120+ RESULT , Collections .singletonList (TOPIC_PARTITION ), (short ) 2 )
121+ );
122+ WriteTxnMarkersRequest .Builder builder = new WriteTxnMarkersRequest .Builder (markersWithVersion );
123+
124+ // Test request version 2 - TransactionVersion should be included.
125+ WriteTxnMarkersRequest requestV2 = builder .build ((short ) 2 );
126+ assertNotNull (requestV2 );
127+ assertEquals (1 , requestV2 .markers ().size ());
128+
129+ // Verify TransactionVersion is set to 2 in the request data.
130+ assertEquals ((byte ) 2 , requestV2 .data ().markers ().get (0 ).transactionVersion ());
131+ // Verify the request can be serialized for version 2 (TransactionVersion field included).
132+ // This should not throw an exception.
133+ ByteBufferAccessor serializedV2 = requestV2 .serialize ();
134+ assertNotNull (serializedV2 , "Serialization should succeed without error for version 2" );
135+ // Test deserialization for version 2 - verify TransactionVersion field was included during serialization.
136+ // Use the already serialized request and parse it back to verify the field is present.
137+ serializedV2 .buffer ().rewind ();
138+ RequestAndSize requestAndSizeV2 = AbstractRequest .parseRequest (
139+ ApiKeys .WRITE_TXN_MARKERS , (short ) 2 , serializedV2 );
140+ WriteTxnMarkersRequest parsedRequestV2 = (WriteTxnMarkersRequest ) requestAndSizeV2 .request ;
141+ assertNotNull (parsedRequestV2 );
142+ assertEquals (1 , parsedRequestV2 .markers ().size ());
143+ // After deserialization, TransactionVersion should be 2 because it was included during serialization.
144+ assertEquals ((short ) 2 , parsedRequestV2 .markers ().get (0 ).transactionVersion ());
145+ // Verify the data also shows 2 (since it was read from serialized bytes with the field).
146+ assertEquals ((byte ) 2 , parsedRequestV2 .data ().markers ().get (0 ).transactionVersion ());
147+
148+ // Test request version 1 - TransactionVersion should be omitted (ignorable field).
149+ WriteTxnMarkersRequest requestV1 = builder .build ((short ) 1 );
150+ assertNotNull (requestV1 );
151+ assertEquals (1 , requestV1 .markers ().size ());
152+
153+ // Verify TransactionVersion is still set to 2 in the request data (even for version 1).
154+ // This is what the coordinator has when building the request - data() is used before serialization.
155+ // The field value is preserved in the data, but will be omitted during serialization.
156+ assertEquals ((byte ) 2 , requestV1 .data ().markers ().get (0 ).transactionVersion ());
157+ // Verify the request can be serialized for version 1 (TransactionVersion field omitted).
158+ // This should not throw an exception even though TransactionVersion is set to 2
159+ // because the field is marked as ignorable.
160+ ByteBufferAccessor serializedV1 = requestV1 .serialize ();
161+ assertNotNull (serializedV1 , "Serialization should succeed without error for version 1 even with TransactionVersion set" );
162+ // Test deserialization for version 1 - verify TransactionVersion field was omitted during serialization.
163+ // Use the already serialized request and parse it back to verify the field is not present.
164+ serializedV1 .buffer ().rewind ();
165+ RequestAndSize requestAndSizeV1 = AbstractRequest .parseRequest (
166+ ApiKeys .WRITE_TXN_MARKERS , (short ) 1 , serializedV1 );
167+ WriteTxnMarkersRequest parsedRequestV1 = (WriteTxnMarkersRequest ) requestAndSizeV1 .request ;
168+ assertNotNull (parsedRequestV1 );
169+ assertEquals (1 , parsedRequestV1 .markers ().size ());
170+ // After deserialization, TransactionVersion should be 0 because it was omitted during serialization.
171+ // The field is not present in the serialized bytes for version 1, so it defaults to 0.
172+ assertEquals ((short ) 0 , parsedRequestV1 .markers ().get (0 ).transactionVersion ());
173+ // Verify the data also shows 0 (since it was read from serialized bytes without the field).
174+ assertEquals ((byte ) 0 , parsedRequestV1 .data ().markers ().get (0 ).transactionVersion ());
175+ }
176+
177+ @ Test
178+ public void testRequestWithMultipleMarkersDifferentTransactionVersions () {
179+ // Test building a request with two markers - one with tv1 and one with tv2
180+ // and verify that the right transaction versions are updated in the request data
181+ TopicPartition topicPartition1 = new TopicPartition ("topic1" , 0 );
182+ TopicPartition topicPartition2 = new TopicPartition ("topic2" , 1 );
183+ long producerId1 = 100L ;
184+ long producerId2 = 200L ;
185+
186+ List <WriteTxnMarkersRequest .TxnMarkerEntry > markersWithDifferentVersions = List .of (
187+ new WriteTxnMarkersRequest .TxnMarkerEntry (
188+ producerId1 , PRODUCER_EPOCH , COORDINATOR_EPOCH ,
189+ RESULT , Collections .singletonList (topicPartition1 ), (short ) 1 ), // tv1
190+ new WriteTxnMarkersRequest .TxnMarkerEntry (
191+ producerId2 , PRODUCER_EPOCH , COORDINATOR_EPOCH ,
192+ RESULT , Collections .singletonList (topicPartition2 ), (short ) 2 ) // tv2
193+ );
194+
195+ WriteTxnMarkersRequest .Builder builder = new WriteTxnMarkersRequest .Builder (markersWithDifferentVersions );
196+ WriteTxnMarkersRequest request = builder .build ((short ) 2 );
197+
198+ assertNotNull (request );
199+ assertEquals (2 , request .data ().markers ().size ());
200+
201+ // Verify first marker has tv1 (transactionVersion = 1) in the request data
202+ WriteTxnMarkersRequestData .WritableTxnMarker dataMarker1 = request .data ().markers ().get (0 );
203+ assertEquals (producerId1 , dataMarker1 .producerId ());
204+ assertEquals ((byte ) 1 , dataMarker1 .transactionVersion ());
205+
206+ // Verify second marker has tv2 (transactionVersion = 2) in the request data
207+ WriteTxnMarkersRequestData .WritableTxnMarker dataMarker2 = request .data ().markers ().get (1 );
208+ assertEquals (producerId2 , dataMarker2 .producerId ());
209+ assertEquals ((byte ) 2 , dataMarker2 .transactionVersion ());
210+
211+ // Verify markers() method also returns correct transaction versions
212+ List <WriteTxnMarkersRequest .TxnMarkerEntry > markers = request .markers ();
213+ assertEquals (2 , markers .size ());
214+ assertEquals ((short ) 1 , markers .get (0 ).transactionVersion ());
215+ assertEquals (producerId1 , markers .get (0 ).producerId ());
216+ assertEquals ((short ) 2 , markers .get (1 ).transactionVersion ());
217+ assertEquals (producerId2 , markers .get (1 ).producerId ());
218+ }
82219}
0 commit comments