1414 */
1515package com .github .ambry .frontend ;
1616import com .github .ambry .commons .InMemNamedBlobDbFactory ;
17+ import com .github .ambry .commons .RetainingAsyncWritableChannel ;
18+ import io .netty .buffer .ByteBuf ;
19+ import io .netty .buffer .Unpooled ;
1720import java .nio .charset .StandardCharsets ;
1821
1922import com .codahale .metrics .MetricRegistry ;
4245import com .github .ambry .router .FutureResult ;
4346import com .github .ambry .router .InMemoryRouter ;
4447import com .github .ambry .router .ReadableStreamChannel ;
48+ import com .github .ambry .utils .NettyByteBufLeakHelper ;
4549import com .github .ambry .utils .TestUtils ;
4650import java .nio .ByteBuffer ;
4751import java .util .Arrays ;
4852import java .util .Collections ;
4953import java .util .LinkedList ;
5054import java .util .Properties ;
5155import org .json .JSONObject ;
56+ import org .junit .After ;
57+ import org .junit .Before ;
5258import org .junit .Test ;
59+ import org .mockito .Mockito ;
5360
5461import static com .github .ambry .frontend .s3 .S3Constants .*;
5562import static org .junit .Assert .*;
63+ import static org .mockito .Mockito .*;
5664
5765
5866public class S3BatchDeleteHandlerTest {
@@ -67,6 +75,8 @@ public class S3BatchDeleteHandlerTest {
6775 private FrontendConfig frontendConfig ;
6876 private NamedBlobPutHandler namedBlobPutHandler ;
6977 private S3BatchDeleteHandler s3BatchDeleteHandler ;
78+ private final NettyByteBufLeakHelper nettyByteBufLeakHelper = new NettyByteBufLeakHelper ();
79+
7080
7181 public S3BatchDeleteHandlerTest () throws Exception {
7282 account = ACCOUNT_SERVICE .createAndAddRandomAccount ();
@@ -83,6 +93,16 @@ public S3BatchDeleteHandlerTest() throws Exception {
8393 performPutOperation (KEY_NAME_3 , KEY_NAME , container , account );
8494 }
8595
96+ @ Before
97+ public void before () {
98+ nettyByteBufLeakHelper .beforeTest ();
99+ }
100+
101+ @ After
102+ public void after () {
103+ nettyByteBufLeakHelper .afterTest ();
104+ }
105+
86106 @ Test
87107 public void deleteObjectTest () throws Exception {
88108 String uri = String .format ("/s3/%s/%s" , account .getName (), container .getName ());
@@ -123,6 +143,74 @@ public void deleteObjectTest() throws Exception {
123143 assertEquals ("Mismatch on status" , ResponseStatus .Ok , restResponseChannel .getStatus ());
124144 }
125145
146+ @ Test
147+ public void testBufferReleaseInDeserializeRequest () throws Exception {
148+ // Arrange
149+ S3BatchDeleteHandler handler = new S3BatchDeleteHandler (null , null );
150+ RetainingAsyncWritableChannel channel = mock (RetainingAsyncWritableChannel .class );
151+ ByteBuf mockByteBuf = mock (ByteBuf .class );
152+
153+ String validXml = "<?xml version=\" 1.0\" encoding=\" UTF-8\" ?>" +
154+ "<Delete xmlns=\" http://s3.amazonaws.com/doc/2006-03-01\" >" +
155+ "<Object>" +
156+ "<Key>key-success</Key>" +
157+ "</Object>" +
158+ "<Object>" +
159+ "<Key>key-error</Key>" +
160+ "</Object>" +
161+ "<Object>" +
162+ "<Key>key-error2</Key>" +
163+ "</Object>" +
164+ "<Object>" +
165+ "<Key>key-success-2</Key>" +
166+ "</Object>" +
167+ "</Delete>" ;
168+
169+ byte [] xmlBytes = validXml .getBytes ();
170+
171+ when (channel .consumeContentAsByteBuf ()).thenReturn (mockByteBuf );
172+ when (mockByteBuf .readableBytes ()).thenReturn (xmlBytes .length );
173+ doAnswer (invocation -> {
174+ byte [] buffer = invocation .getArgument (0 );
175+ System .arraycopy (xmlBytes , 0 , buffer , 0 , xmlBytes .length );
176+ return null ;
177+ }).when (mockByteBuf ).readBytes (Mockito .any (byte [].class ));
178+
179+ S3MessagePayload .S3BatchDeleteObjects result = handler .deserializeRequest (channel );
180+
181+ assertNotNull (result );
182+ verify (mockByteBuf , times (1 )).release (); // Verify that release() was called
183+ }
184+
185+ @ Test
186+ public void testDeserializeRequestWithRealChannel () throws Exception {
187+ // Arrange
188+ String validXml =
189+ "<?xml version=\" 1.0\" encoding=\" UTF-8\" ?>" + "<Delete xmlns=\" http://s3.amazonaws.com/doc/2006-03-01\" >"
190+ + "<Object>" + "<Key>key-success</Key>" + "</Object>" + "<Object>" + "<Key>key-error</Key>" + "</Object>"
191+ + "</Delete>" ;
192+
193+ ByteBuf byteBuf = Unpooled .wrappedBuffer (validXml .getBytes ());
194+ RetainingAsyncWritableChannel channel = new RetainingAsyncWritableChannel ();
195+ channel .write (byteBuf , (result , exception ) -> {
196+ if (exception != null ) {
197+ fail ("Failed to write to channel: " + exception .getMessage ());
198+ }
199+ });
200+
201+ S3BatchDeleteHandler handler = new S3BatchDeleteHandler (null , null );
202+
203+ // Act
204+ S3MessagePayload .S3BatchDeleteObjects result = handler .deserializeRequest (channel );
205+
206+ // Assert
207+ assertNotNull (result );
208+ assertEquals (2 , result .getObjects ().size ());
209+ assertEquals ("key-success" , result .getObjects ().get (0 ).getKey ());
210+ assertEquals ("key-error" , result .getObjects ().get (1 ).getKey ());
211+ }
212+
213+
126214 @ Test
127215 public void malformedXMLRequestTest () throws Exception {
128216 String uri = String .format ("/s3/%s/%s" , account .getName (), container .getName ());
0 commit comments