View Javadoc
1   /*
2    * Copyright (c) 2011-2022, jcabi.com
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met: 1) Redistributions of source code must retain the above
8    * copyright notice, this list of conditions and the following
9    * disclaimer. 2) Redistributions in binary form must reproduce the above
10   * copyright notice, this list of conditions and the following
11   * disclaimer in the documentation and/or other materials provided
12   * with the distribution. 3) Neither the name of the jcabi.com nor
13   * the names of its contributors may be used to endorse or promote
14   * products derived from this software without specific prior written
15   * permission.
16   *
17   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
19   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28   * OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  
31  package com.jcabi.http;
32  
33  import com.jcabi.http.request.BaseRequest;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.net.URI;
37  import java.util.Collection;
38  import java.util.Map;
39  import java.util.concurrent.Callable;
40  import java.util.function.Supplier;
41  import org.hamcrest.MatcherAssert;
42  import org.hamcrest.Matchers;
43  import org.junit.jupiter.api.Test;
44  import org.junit.jupiter.params.ParameterizedTest;
45  import org.mockito.ArgumentCaptor;
46  import org.mockito.ArgumentMatchers;
47  import org.mockito.Mockito;
48  
49  /**
50   * Test case for loss of timeout parameters.
51   * @since 1.17.3
52   */
53  @SuppressWarnings("PMD.TooManyMethods")
54  final class RequestTimeoutLossTest extends RequestTestTemplate {
55      /**
56       * Placeholder URL used for testing purposes only.
57       */
58      private static final String LOCALHOST_URL = "http://localhost";
59  
60      /**
61       * Content type header name for testing purposes only.
62       */
63      private static final String CONTENT_TYPE = "Content-Type";
64  
65      /**
66       * Magic number for connection timeout.
67       */
68      private static final int CONNECT_TIMEOUT = 1234;
69  
70      /**
71       * Magic number for read timeout.
72       */
73      private static final int READ_TIMEOUT = 2345;
74  
75      /**
76       * The connect and read timeouts are properly set no matter in which order
77       * <code>Request.timeout</code> is called.
78       *
79       * @throws Exception If something goes wrong inside
80       * @param type Type of Request
81       */
82      @ParameterizedTest
83      @Values
84      void testTimeoutOrderDoesntMatterBeforeBody(
85          final Class<? extends Request> type
86      )
87          throws Exception {
88          final Callable<Response> execution = new Callable<Response>() {
89              @Override
90              public Response call() throws Exception {
91                  return RequestTimeoutLossTest.request(type)
92                      .through(MockWire.class)
93                      .method(Request.GET)
94                      .timeout(
95                          RequestTimeoutLossTest.CONNECT_TIMEOUT,
96                          RequestTimeoutLossTest.READ_TIMEOUT
97                      )
98                      .body()
99                      .back()
100                     .fetch();
101             }
102         };
103         this.testTimeoutOrderDoesntMatter(execution);
104     }
105 
106     /**
107      * The connect and read timeouts are properly set no matter in which order
108      * <code>Request.timeout</code> is called.
109      *
110      * @throws Exception If something goes wrong inside
111      * @param type Type of Request
112      */
113     @ParameterizedTest
114     @Values
115     void testTimeoutOrderDoesntMatterBeforeFetch(
116         final Class<? extends Request> type
117     )
118         throws Exception {
119         final Callable<Response> execution = new Callable<Response>() {
120             @Override
121             public Response call() throws Exception {
122                 return RequestTimeoutLossTest.request(type)
123                     .through(MockWire.class)
124                     .method(Request.GET)
125                     .timeout(
126                         RequestTimeoutLossTest.CONNECT_TIMEOUT,
127                         RequestTimeoutLossTest.READ_TIMEOUT
128                     )
129                     .fetch();
130             }
131         };
132         this.testTimeoutOrderDoesntMatter(execution);
133     }
134 
135     /**
136      * The connect and read timeouts are properly set no matter in which order
137      * <code>Request.timeout</code> is called.
138      *
139      * @throws Exception If something goes wrong inside
140      * @param type Type of Request
141      */
142     @ParameterizedTest
143     @Values
144     void testTimeoutOrderDoesntMatterBeforeHeader(
145         final Class<? extends Request> type
146     )
147         throws Exception {
148         final Callable<Response> execution = new Callable<Response>() {
149             @Override
150             public Response call() throws Exception {
151                 return RequestTimeoutLossTest.request(type)
152                     .through(MockWire.class)
153                     .method(Request.GET)
154                     .timeout(
155                         RequestTimeoutLossTest.CONNECT_TIMEOUT,
156                         RequestTimeoutLossTest.READ_TIMEOUT
157                     )
158                     .header(
159                         RequestTimeoutLossTest.CONTENT_TYPE,
160                         "text/plain"
161                     )
162                     .fetch();
163             }
164         };
165         this.testTimeoutOrderDoesntMatter(execution);
166     }
167 
168     /**
169      * The connect and read timeouts are properly set no matter in which order
170      * <code>Request.timeout</code> is called.
171      *
172      * @throws Exception If something goes wrong inside
173      * @param type Type of Request
174      */
175     @ParameterizedTest
176     @Values
177     void testTimeoutOrderDoesntMatterBeforeMethod(
178         final Class<? extends Request> type
179     )
180         throws Exception {
181         final Callable<Response> execution = new Callable<Response>() {
182             @Override
183             public Response call() throws Exception {
184                 return RequestTimeoutLossTest.request(type)
185                     .through(MockWire.class)
186                     .timeout(
187                         RequestTimeoutLossTest.CONNECT_TIMEOUT,
188                         RequestTimeoutLossTest.READ_TIMEOUT
189                     )
190                     .method(Request.GET)
191                     .fetch();
192             }
193         };
194         this.testTimeoutOrderDoesntMatter(execution);
195     }
196 
197     /**
198      * The connect and read timeouts are properly set no matter in which order
199      * <code>Request.timeout</code> is called.
200      *
201      * @throws Exception If something goes wrong inside
202      * @param type Type of Request
203      */
204     @ParameterizedTest
205     @Values
206     void testTimeoutOrderDoesntMatterBeforeMultipartBody(
207         final Class<? extends Request> type
208     )
209         throws Exception {
210         final Callable<Response> execution = new Callable<Response>() {
211             @Override
212             public Response call() throws Exception {
213                 return RequestTimeoutLossTest.request(type)
214                     .through(MockWire.class)
215                     .method(Request.GET)
216                     .timeout(
217                         RequestTimeoutLossTest.CONNECT_TIMEOUT,
218                         RequestTimeoutLossTest.READ_TIMEOUT
219                     )
220                     .multipartBody()
221                     .back()
222                     .fetch();
223             }
224         };
225         this.testTimeoutOrderDoesntMatter(execution);
226     }
227 
228     /**
229      * The connect and read timeouts are properly set no matter in which order
230      * <code>Request.timeout</code> is called.
231      *
232      * @throws Exception If something goes wrong inside
233      * @param type Type of Request
234      */
235     @ParameterizedTest
236     @Values
237     void testTimeoutOrderDoesntMatterBeforeReset(
238         final Class<? extends Request> type
239     )
240         throws Exception {
241         final Callable<Response> execution = new Callable<Response>() {
242             @Override
243             public Response call() throws Exception {
244                 return RequestTimeoutLossTest.request(type)
245                     .through(MockWire.class)
246                     .method(Request.GET)
247                     .timeout(
248                         RequestTimeoutLossTest.CONNECT_TIMEOUT,
249                         RequestTimeoutLossTest.READ_TIMEOUT
250                     )
251                     .reset(RequestTimeoutLossTest.CONTENT_TYPE)
252                     .fetch();
253             }
254         };
255         this.testTimeoutOrderDoesntMatter(execution);
256     }
257 
258     /**
259      * The connect and read timeouts are properly set no matter in which order
260      * <code>Request.timeout</code> is called.
261      *
262      * @throws Exception If something goes wrong inside
263      * @param type Type of Request
264      */
265     @ParameterizedTest
266     @Values
267     void testTimeoutOrderDoesntMatterBeforeUriBack(
268         final Class<? extends Request> type
269     )
270         throws Exception {
271         this.testTimeoutOrderDoesntMatter(
272             new Callable<Response>() {
273                 @Override
274                 public Response call() throws Exception {
275                     return RequestTimeoutLossTest.request(type)
276                         .through(MockWire.class)
277                         .method(Request.GET)
278                         .timeout(
279                             RequestTimeoutLossTest.CONNECT_TIMEOUT,
280                             RequestTimeoutLossTest.READ_TIMEOUT
281                         )
282                         .uri()
283                         .path("/api")
284                         .back()
285                         .fetch();
286                 }
287             }
288         );
289     }
290 
291     /**
292      * The wire passed to method "through" is used.
293      *
294      * @throws IOException On error
295      */
296     @Test
297     void passesThroughWire() throws IOException {
298         final Wire original = Mockito.mock(Wire.class);
299         final Wire wire = Mockito.mock(Wire.class);
300         final Response response = Mockito.mock(Response.class);
301         final Supplier<Collection<Map.Entry<String, String>>> hdrs =
302             new Supplier<Collection<Map.Entry<String, String>>>() {
303                 @Override
304                 public Collection<Map.Entry<String, String>> get() {
305                     return ArgumentMatchers.anyCollection();
306                 }
307             };
308         final String url = "fake-url";
309         Mockito.when(
310             wire.send(
311                 ArgumentMatchers.any(Request.class),
312                 ArgumentMatchers.eq(url),
313                 ArgumentMatchers.anyString(),
314                 hdrs.get(),
315                 ArgumentMatchers.any(InputStream.class),
316                 ArgumentMatchers.anyInt(),
317                 ArgumentMatchers.anyInt()
318             )
319         ).thenReturn(response);
320         new BaseRequest(original, url).through(wire).fetch();
321         Mockito.verify(original, Mockito.never()).send(
322             ArgumentMatchers.any(Request.class),
323             ArgumentMatchers.anyString(),
324             ArgumentMatchers.anyString(),
325             hdrs.get(),
326             ArgumentMatchers.any(InputStream.class),
327             ArgumentMatchers.anyInt(),
328             ArgumentMatchers.anyInt()
329         );
330         Mockito.verify(wire).send(
331             ArgumentMatchers.any(Request.class),
332             ArgumentMatchers.anyString(),
333             ArgumentMatchers.anyString(),
334             hdrs.get(),
335             ArgumentMatchers.any(InputStream.class),
336             ArgumentMatchers.anyInt(),
337             ArgumentMatchers.anyInt()
338         );
339     }
340 
341     /**
342      * The connect and read timeouts are properly set no matter in which order
343      * <code>Request.timeout</code> is called.
344      *
345      * @param exec The callable that contains the actual request
346      * @throws Exception If something goes wrong inside
347      */
348     @SuppressWarnings("unchecked")
349     private void testTimeoutOrderDoesntMatter(final Callable<Response> exec)
350         throws Exception {
351         synchronized (MockWire.class) {
352             final Wire wire = Mockito.mock(Wire.class);
353             final ArgumentCaptor<Integer> cnc = ArgumentCaptor
354                 .forClass(Integer.class);
355             final ArgumentCaptor<Integer> rdc = ArgumentCaptor
356                 .forClass(Integer.class);
357             MockWire.setMockDelegate(wire);
358             final Response response = Mockito.mock(Response.class);
359             Mockito.when(
360                 wire.send(
361                     Mockito.any(Request.class),
362                     Mockito.anyString(),
363                     Mockito.anyString(),
364                     Mockito.<Map.Entry<String, String>>anyCollection(),
365                     Mockito.any(InputStream.class),
366                     Mockito.anyInt(),
367                     Mockito.anyInt()
368                 )
369             ).thenReturn(response);
370             exec.call();
371             Mockito.verify(wire).send(
372                 Mockito.any(Request.class),
373                 Mockito.anyString(),
374                 Mockito.anyString(),
375                 Mockito.<Map.Entry<String, String>>anyCollection(),
376                 Mockito.any(InputStream.class),
377                 cnc.capture(),
378                 rdc.capture()
379             );
380             MatcherAssert.assertThat(
381                 cnc.getValue().intValue(),
382                 Matchers.is(RequestTimeoutLossTest.CONNECT_TIMEOUT)
383             );
384             MatcherAssert.assertThat(
385                 rdc.getValue().intValue(),
386                 Matchers.is(RequestTimeoutLossTest.READ_TIMEOUT)
387             );
388         }
389     }
390 
391     /**
392      * Make a request with default url.
393      * @param type Type of Request
394      * @return Request
395      * @throws Exception If fails
396      */
397     private static Request request(final Class<? extends Request> type)
398         throws Exception {
399         return RequestTestTemplate.request(
400             new URI(RequestTimeoutLossTest.LOCALHOST_URL),
401             type
402         );
403     }
404 
405 }