2929import static org .junit .jupiter .api .Assertions .assertEquals ;
3030import static org .junit .jupiter .api .Assertions .assertFalse ;
3131import static org .junit .jupiter .api .Assertions .assertNotNull ;
32+ import static org .junit .jupiter .api .Assertions .assertNull ;
3233import static org .junit .jupiter .api .Assertions .assertSame ;
3334import static org .junit .jupiter .api .Assertions .assertThrows ;
3435import static org .junit .jupiter .api .Assertions .assertTrue ;
@@ -130,7 +131,8 @@ void testDeflateConsumerInserted() throws Exception {
130131 when (details .getContentEncoding ()).thenReturn ("deflate" );
131132
132133 final AsyncDataConsumer downstream = new StringAsyncEntityConsumer ();
133- when (originalCb .handleResponse (same (rsp ), same (details ))).thenReturn (downstream );
134+ // the exec passes a wrapped EntityDetails downstream, so match any (not same(details))
135+ when (originalCb .handleResponse (same (rsp ), any (EntityDetails .class ))).thenReturn (downstream );
134136
135137 final AsyncDataConsumer wrapped = cb .handleResponse (rsp , details );
136138
@@ -172,6 +174,10 @@ public AsyncDataConsumer apply(final AsyncDataConsumer d) {
172174 final HttpResponse rsp = new BasicHttpResponse (200 , "OK" );
173175 final EntityDetails details = mock (EntityDetails .class );
174176 when (details .getContentEncoding ()).thenReturn ("whatever" );
177+ // a real (non-null) downstream so the exec proceeds to decoder selection and rejects the
178+ // unknown coding; a null downstream would legitimately be discarded without decoding
179+ when (originalCb .handleResponse (same (rsp ), any (EntityDetails .class )))
180+ .thenReturn (new StringAsyncEntityConsumer ());
175181
176182 assertThrows (HttpException .class , () -> cb .handleResponse (rsp , details ));
177183 }
@@ -197,7 +203,7 @@ void testContentEncodingExceedsCodecListLenMax() throws Exception {
197203 when (details1 .getContentEncoding ()).thenReturn ("gzip,gzip,gzip,gzip,gzip" );
198204
199205 final AsyncDataConsumer downstream1 = new StringAsyncEntityConsumer ();
200- when (originalCb .handleResponse (same (rsp1 ), same ( details1 ))).thenReturn (downstream1 );
206+ when (originalCb .handleResponse (same (rsp1 ), any ( EntityDetails . class ))).thenReturn (downstream1 );
201207
202208 final AsyncDataConsumer wrapped = cb .handleResponse (rsp1 , details1 );
203209
@@ -214,4 +220,20 @@ void testContentEncodingExceedsCodecListLenMax() throws Exception {
214220 assertEquals ("Codec list exceeds maximum of 5 elements" , exception .getMessage ());
215221 }
216222
223+ @ Test
224+ void propagatesNullDiscardOnEncodedResponse () throws Exception {
225+ final HttpRequest request = new BasicHttpRequest (Method .GET , "/" );
226+ final AsyncExecCallback cb = executeAndCapture (request );
227+
228+ final HttpResponse rsp = new BasicHttpResponse (302 , "Found" );
229+ final EntityDetails details = mock (EntityDetails .class );
230+ when (details .getContentEncoding ()).thenReturn ("gzip" );
231+
232+ // an upstream exec (e.g. AsyncRedirectExec on a redirect) discards the body by returning null
233+ when (originalCb .handleResponse (same (rsp ), any (EntityDetails .class ))).thenReturn (null );
234+
235+ // the null-discard signal must be propagated, not wrapped in a decoder (HTTPCLIENT-2426)
236+ assertNull (cb .handleResponse (rsp , details ));
237+ }
238+
217239}
0 commit comments