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  package com.jcabi.http;
31  
32  import com.google.common.base.Joiner;
33  import com.jcabi.http.mock.MkAnswer;
34  import com.jcabi.http.mock.MkContainer;
35  import com.jcabi.http.mock.MkGrizzlyContainer;
36  import com.jcabi.http.mock.MkQuery;
37  import com.jcabi.http.mock.MkQueryMatchers;
38  import com.jcabi.http.response.RestResponse;
39  import com.jcabi.http.response.XmlResponse;
40  import com.jcabi.http.wire.BasicAuthWire;
41  import com.jcabi.http.wire.UserAgentWire;
42  import jakarta.ws.rs.core.HttpHeaders;
43  import jakarta.ws.rs.core.MediaType;
44  import jakarta.ws.rs.core.UriBuilder;
45  import java.io.ByteArrayInputStream;
46  import java.net.HttpURLConnection;
47  import java.net.URI;
48  import java.net.URLDecoder;
49  import java.net.URLEncoder;
50  import java.nio.charset.StandardCharsets;
51  import org.glassfish.grizzly.http.server.Constants;
52  import org.hamcrest.MatcherAssert;
53  import org.hamcrest.Matchers;
54  import org.junit.jupiter.api.Assertions;
55  import org.junit.jupiter.api.function.Executable;
56  import org.junit.jupiter.params.ParameterizedTest;
57  
58  /**
59   * Test case for {@link Request} and its implementations.
60   * @since 1.7
61   */
62  @SuppressWarnings(
63      {
64          "PMD.TooManyMethods",
65          "PMD.AvoidDuplicateLiterals",
66          "PMD.TestClassWithoutTestCases"
67      })
68  final class RequestTest extends RequestTestTemplate {
69  
70      /**
71       * BaseRequest can fetch HTTP request and process HTTP response.
72       * @throws Exception If something goes wrong inside
73       * @param type Request type
74       */
75      @Values
76      @ParameterizedTest
77      void sendsHttpRequestAndProcessesHttpResponse(
78          final Class<? extends Request> type
79      ) throws Exception {
80          final MkContainer container = new MkGrizzlyContainer().next(
81              new MkAnswer.Simple("\u20ac! hello!")
82          ).start();
83          RequestTestTemplate.request(container.home(), type)
84              .uri().path("/helloall").back()
85              .method(Request.GET)
86              .fetch().as(RestResponse.class)
87              .assertBody(Matchers.containsString("\u20ac!"))
88              .assertBody(Matchers.containsString("hello!"))
89              .assertStatus(HttpURLConnection.HTTP_OK);
90          final MkQuery query = container.take();
91          MatcherAssert.assertThat(
92              query,
93              MkQueryMatchers.hasPath(Matchers.containsString("helloall"))
94          );
95          MatcherAssert.assertThat(
96              query.method(),
97              Matchers.equalTo(Request.GET)
98          );
99          container.stop();
100     }
101 
102     /**
103      * BaseRequest can fetch HTTP headers.
104      * @throws Exception If something goes wrong inside
105      * @param type Request type
106      */
107     @Values
108     @ParameterizedTest
109     void sendsHttpRequestWithHeaders(
110         final Class<? extends Request> type
111     ) throws Exception {
112         final MkContainer container = new MkGrizzlyContainer().next(
113             new MkAnswer.Simple("")
114         ).start();
115         RequestTestTemplate.request(container.home(), type)
116             .through(UserAgentWire.class)
117             .uri().path("/foo1").back()
118             .method(Request.GET)
119             .header(HttpHeaders.ACCEPT, "*/*")
120             .fetch().as(RestResponse.class)
121             .assertStatus(HttpURLConnection.HTTP_OK);
122         final MkQuery query = container.take();
123         MatcherAssert.assertThat(
124             query.headers(),
125             Matchers.allOf(
126                 Matchers.hasEntry(
127                     Matchers.equalTo(HttpHeaders.ACCEPT),
128                     Matchers.hasItem(Matchers.containsString("*"))
129                 ),
130                 Matchers.hasEntry(
131                     Matchers.equalTo(HttpHeaders.USER_AGENT),
132                     Matchers.hasItem(Matchers.containsString("jcabi"))
133                 )
134             )
135         );
136         container.stop();
137     }
138 
139     /**
140      * BaseRequest can fetch GET request with query params.
141      * @throws Exception If something goes wrong inside
142      * @param type Request type
143      */
144     @Values
145     @ParameterizedTest
146     void sendsTextWithGetParameters(
147         final Class<? extends Request> type
148     ) throws Exception {
149         final MkContainer container = new MkGrizzlyContainer().next(
150             new MkAnswer.Simple("")
151         ).start();
152         final String value = "some value of this param &^%*;'\"\u20ac\"";
153         RequestTestTemplate.request(container.home(), type)
154             .uri().queryParam("q", value).back()
155             .method(Request.GET)
156             .header(HttpHeaders.ACCEPT, MediaType.TEXT_XML)
157             .fetch().as(RestResponse.class)
158             .assertStatus(HttpURLConnection.HTTP_OK);
159         final MkQuery query = container.take();
160         MatcherAssert.assertThat(
161             URLDecoder.decode(
162                 query.uri().toString(),
163                 String.valueOf(StandardCharsets.UTF_8)
164             ),
165             Matchers.endsWith("\"€\"")
166         );
167         container.stop();
168     }
169 
170     /**
171      * BaseRequest can fetch body with HTTP POST request.
172      * @throws Exception If something goes wrong inside
173      * @param type Request type
174      */
175     @Values
176     @ParameterizedTest
177     void sendsTextWithPostRequestMatchParam(
178         final Class<? extends Request> type
179     ) throws Exception {
180         final MkContainer container = new MkGrizzlyContainer().next(
181             new MkAnswer.Simple("")
182         ).start();
183         final String value = "some random value of \u20ac param \"&^%*;'\"";
184         RequestTestTemplate.request(container.home(), type)
185             .method(Request.POST)
186             .body().formParam("p", value).back()
187             .header(
188                 HttpHeaders.CONTENT_TYPE,
189                 MediaType.APPLICATION_FORM_URLENCODED
190             )
191             .fetch().as(RestResponse.class)
192             .assertStatus(HttpURLConnection.HTTP_OK);
193         final MkQuery query = container.take();
194         MatcherAssert.assertThat(
195             URLDecoder.decode(query.body(), StandardCharsets.UTF_8.toString()),
196             Matchers.is(String.format("p=%s", value))
197         );
198         container.stop();
199     }
200 
201     /**
202      * BaseRequest can fetch body with HTTP POST request with params.
203      * @throws Exception If something goes wrong inside
204      * @param type Request type
205      */
206     @Values
207     @ParameterizedTest
208     void sendsTextWithPostRequestMatchMultipleParams(
209         final Class<? extends Request> type
210     ) throws Exception {
211         final MkContainer container = new MkGrizzlyContainer().next(
212             new MkAnswer.Simple("")
213         ).start();
214         final String value = "some value of \u20ac param \"&^%*;'\"";
215         final String follow = "other value of \u20ac param \"&^%*;'\"";
216         RequestTestTemplate.request(container.home(), type)
217             .method(Request.POST)
218             .body()
219             .formParam("a", value)
220             .formParam("b", follow)
221             .back()
222             .header(
223                 HttpHeaders.CONTENT_TYPE,
224                 MediaType.APPLICATION_FORM_URLENCODED
225             )
226             .fetch().as(RestResponse.class)
227             .assertStatus(HttpURLConnection.HTTP_OK);
228         final MkQuery query = container.take();
229         MatcherAssert.assertThat(
230             URLDecoder.decode(query.body(), StandardCharsets.UTF_8.toString()),
231             Matchers.is(
232                 String.format("a=%s&b=%s", value, follow)
233             )
234         );
235         container.stop();
236     }
237 
238     /**
239      * BaseRequest can fetch multipart body with HTTP POST request
240      * with single byte param.
241      * @throws Exception If something goes wrong inside
242      * @checkstyle LineLength (30 lines)
243      * @param type Request type
244      */
245     @Values
246     @ParameterizedTest
247     void sendsMultipartPostRequestMatchByteParam(
248         final Class<? extends Request> type
249     ) throws Exception {
250         final MkContainer container = new MkGrizzlyContainer().next(
251             new MkAnswer.Simple("")
252         ).start();
253         final byte[] value = new byte[]{Byte.parseByte("-122")};
254         RequestTestTemplate.request(container.home(), type)
255             .method(Request.POST)
256             .header(
257                 HttpHeaders.CONTENT_TYPE,
258                 String.format(
259                     "%s; boundary=--xx", MediaType.MULTIPART_FORM_DATA
260                 )
261             )
262             .multipartBody()
263             .formParam("x", value)
264             .back()
265             .fetch().as(RestResponse.class)
266             .assertStatus(HttpURLConnection.HTTP_OK);
267         final MkQuery query = container.take();
268         MatcherAssert.assertThat(
269             query.body(),
270             Matchers.is(
271                 Joiner.on(Constants.CRLF).join(
272                     "----xx",
273                     "Content-Disposition: form-data; name=\"x\"; filename=\"binary\"",
274                     RequestTest.steamContentType(),
275                     "",
276                     "�",
277                     "----xx--"
278                 )
279             )
280         );
281         container.stop();
282     }
283 
284     /**
285      * BaseRequest can fetch multipart body with HTTP POST request
286      * with single param.
287      * @throws Exception If something goes wrong inside
288      * @checkstyle LineLength (30 lines)
289      * @param type Request type
290      */
291     @Values
292     @ParameterizedTest
293     void sendsMultipartPostRequestMatchSingleParam(
294         final Class<? extends Request> type
295     ) throws Exception {
296         final MkContainer container = new MkGrizzlyContainer().next(
297             new MkAnswer.Simple("")
298         ).start();
299         final String value = "value of \u20ac part param \"&^%*;'\"";
300         RequestTestTemplate.request(container.home(), type)
301             .method(Request.POST)
302             .header(
303                 HttpHeaders.CONTENT_TYPE,
304                 String.format(
305                     "%s; boundary=--xyz", MediaType.MULTIPART_FORM_DATA
306                 )
307             )
308             .multipartBody()
309             .formParam("c", value)
310             .back()
311             .fetch().as(RestResponse.class)
312             .assertStatus(HttpURLConnection.HTTP_OK);
313         final MkQuery query = container.take();
314         MatcherAssert.assertThat(
315             query.body(),
316             Matchers.is(
317                 Joiner.on(Constants.CRLF).join(
318                     "----xyz",
319                     "Content-Disposition: form-data; name=\"c\"; filename=\"binary\"",
320                     RequestTest.steamContentType(),
321                     "",
322                     "value of € part param \"&^%*;'\"",
323                     "----xyz--"
324                 )
325             )
326         );
327         container.stop();
328     }
329 
330     /**
331      * BaseRequest can fetch multipart body with HTTP POST request
332      * with two params.
333      * @throws Exception If something goes wrong inside
334      * @checkstyle LineLength (40 lines)
335      * @param type Request type
336      */
337     @Values
338     @ParameterizedTest
339     void sendsMultipartPostRequestMatchTwoParams(
340         final Class<? extends Request> type
341     ) throws Exception {
342         final MkContainer container = new MkGrizzlyContainer().next(
343             new MkAnswer.Simple("")
344         ).start();
345         final String value = "value of \u20ac one param \"&^%*;'\"";
346         final String other = "value of \u20ac two param \"&^%*;'\"";
347         RequestTestTemplate.request(container.home(), type)
348             .method(Request.POST)
349             .header(
350                 HttpHeaders.CONTENT_TYPE,
351                 String.format(
352                     "%s; boundary=xy--", MediaType.MULTIPART_FORM_DATA
353                 )
354             )
355             .multipartBody()
356             .formParam("d", value)
357             .formParam("e", other)
358             .back()
359             .fetch().as(RestResponse.class)
360             .assertStatus(HttpURLConnection.HTTP_OK);
361         final MkQuery query = container.take();
362         final String separator = "--xy--";
363         MatcherAssert.assertThat(
364             query.body(),
365             Matchers.is(
366                 Joiner.on(Constants.CRLF).join(
367                     separator,
368                     "Content-Disposition: form-data; name=\"d\"; filename=\"binary\"",
369                     RequestTest.steamContentType(),
370                     "",
371                     "value of € one param \"&^%*;'\"",
372                     separator,
373                     "Content-Disposition: form-data; name=\"e\"; filename=\"binary\"",
374                     RequestTest.steamContentType(),
375                     "",
376                     "value of € two param \"&^%*;'\"",
377                     "--xy----"
378                 )
379             )
380         );
381         container.stop();
382     }
383 
384     /**
385      * BaseRequest can fetch body with HTTP POST request.
386      * @throws Exception If something goes wrong inside
387      * @param type Request type
388      */
389     @Values
390     @ParameterizedTest
391     void sendsTextWithPostRequestMatchBody(
392         final Class<? extends Request> type
393     ) throws Exception {
394         final MkContainer container = new MkGrizzlyContainer().next(
395             new MkAnswer.Simple("")
396         ).start();
397         final String value = "\u20ac some body value with \"&^%*;'\"";
398         RequestTestTemplate.request(container.home(), type)
399             .method(Request.POST)
400             .header(
401                 HttpHeaders.CONTENT_TYPE,
402                 MediaType.APPLICATION_FORM_URLENCODED
403             )
404             .body()
405             .set(URLEncoder.encode(value, StandardCharsets.UTF_8.toString()))
406             .back()
407             .fetch().as(RestResponse.class)
408             .assertStatus(HttpURLConnection.HTTP_OK);
409         final MkQuery query = container.take();
410         MatcherAssert.assertThat(
411             URLDecoder.decode(query.body(), StandardCharsets.UTF_8.toString()),
412             Matchers.containsString(value)
413         );
414         container.stop();
415     }
416 
417     /**
418      * BaseRequest can assert HTTP status code value.
419      * @throws Exception If something goes wrong inside.
420      * @param type Request type
421      */
422     @Values
423     @ParameterizedTest
424     void assertsHttpStatus(
425         final Class<? extends Request> type
426     ) throws Exception {
427         final MkContainer container = new MkGrizzlyContainer().next(
428             new MkAnswer.Simple(HttpURLConnection.HTTP_NOT_FOUND, "")
429         ).start();
430         RequestTestTemplate.request(container.home(), type)
431             .method(Request.GET)
432             .fetch().as(RestResponse.class)
433             .assertStatus(HttpURLConnection.HTTP_NOT_FOUND)
434             .assertStatus(
435                 Matchers.equalTo(HttpURLConnection.HTTP_NOT_FOUND)
436             );
437         container.stop();
438     }
439 
440     /**
441      * BaseRequest can assert response body.
442      * @throws Exception If something goes wrong inside.
443      * @param type Request type
444      */
445     @Values
446     @ParameterizedTest
447     void assertsHttpResponseBody(
448         final Class<? extends Request> type
449     ) throws Exception {
450         final MkContainer container = new MkGrizzlyContainer().next(
451             new MkAnswer.Simple("some text \u20ac")
452         ).start();
453         RequestTestTemplate.request(container.home(), type)
454             .method(Request.GET)
455             .fetch().as(RestResponse.class)
456             .assertBody(Matchers.containsString("text \u20ac"))
457             .assertStatus(HttpURLConnection.HTTP_OK);
458         container.stop();
459     }
460 
461     /**
462      * BaseRequest can assert HTTP headers in response.
463      * @throws Exception If something goes wrong inside.
464      * @param type Request type
465      */
466     @Values
467     @ParameterizedTest
468     void assertsHttpHeaders(
469         final Class<? extends Request> type
470     ) throws Exception {
471         final MkContainer container = new MkGrizzlyContainer().next(
472             new MkAnswer.Simple("").withHeader(
473                 HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN
474             )
475         ).start();
476         RequestTestTemplate.request(container.home(), type)
477             .method(Request.GET)
478             .fetch().as(RestResponse.class)
479             .assertStatus(HttpURLConnection.HTTP_OK)
480             .assertHeader(
481                 "absent-for-sure",
482                 Matchers.emptyIterableOf(String.class)
483             )
484             .assertHeader(
485                 HttpHeaders.CONTENT_TYPE,
486                 Matchers.everyItem(
487                     Matchers.containsString(MediaType.TEXT_PLAIN)
488                 )
489             );
490         container.stop();
491     }
492 
493     /**
494      * BaseRequest can assert response body content with XPath query.
495      * @throws Exception If something goes wrong inside.
496      * @param type Request type
497      */
498     @Values
499     @ParameterizedTest
500     void assertsResponseBodyWithXpathQuery(
501         final Class<? extends Request> type
502     ) throws Exception {
503         final MkContainer container = new MkGrizzlyContainer().next(
504             new MkAnswer.Simple("<root><a>\u0443\u0440\u0430!</a></root>")
505         ).start();
506         RequestTestTemplate.request(container.home(), type)
507             .method(Request.GET)
508             .fetch().as(RestResponse.class)
509             .assertStatus(HttpURLConnection.HTTP_OK)
510             .as(XmlResponse.class)
511             .assertXPath("/root/a[contains(.,'!')]");
512         container.stop();
513     }
514 
515     /**
516      * BaseRequest can work with URL returned by ContainerMocker.
517      * @throws Exception If something goes wrong inside
518      */
519     @Values
520     @ParameterizedTest
521     void mockedUrlIsInCorrectFormat() throws Exception {
522         final MkContainer container = new MkGrizzlyContainer().next(
523             new MkAnswer.Simple("")
524         ).start();
525         container.stop();
526         final URI uri = container.home();
527         MatcherAssert.assertThat(
528             uri.toString().matches("^http://localhost:\\d+/$"),
529             Matchers.describedAs(uri.toString(), Matchers.is(true))
530         );
531     }
532 
533     /**
534      * BaseRequest can handle unicode in plain text response.
535      * @throws Exception If something goes wrong inside
536      * @param type Request type
537      */
538     @Values
539     @ParameterizedTest
540     void acceptsUnicodeInPlainText(
541         final Class<? extends Request> type
542     ) throws Exception {
543         final MkContainer container = new MkGrizzlyContainer().next(
544             new MkAnswer.Simple("\u0443\u0440\u0430!").withHeader(
545                 HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8"
546             )
547         ).start();
548         RequestTestTemplate.request(container.home(), type)
549             .method(Request.GET)
550             .uri().path("/abcdefff").back()
551             .fetch().as(RestResponse.class)
552             .assertBody(Matchers.containsString("\u0443\u0440\u0430"))
553             .assertBody(Matchers.containsString("!"));
554         container.stop();
555     }
556 
557     /**
558      * BaseRequest can handle unicode in XML response.
559      * @throws Exception If something goes wrong inside
560      * @param type Request type
561      */
562     @Values
563     @ParameterizedTest
564     void acceptsUnicodeInXml(
565         final Class<? extends Request> type
566     ) throws Exception {
567         final MkContainer container = new MkGrizzlyContainer().next(
568             new MkAnswer.Simple("<text>\u0443\u0440\u0430!</text>").withHeader(
569                 HttpHeaders.CONTENT_TYPE, "text/xml;charset=utf-8"
570             )
571         ).start();
572         RequestTestTemplate.request(container.home(), type)
573             .method(Request.GET)
574             .uri().path("/barbar").back()
575             .fetch().as(XmlResponse.class)
576             .assertXPath("/text[contains(.,'\u0443\u0440\u0430')]");
577         container.stop();
578     }
579 
580     /**
581      * BaseRequest can use basic authentication scheme.
582      * @throws Exception If something goes wrong inside
583      * @param type Request type
584      */
585     @ParameterizedTest
586     @Values
587     void sendsBasicAuthenticationHeader(
588         final Class<? extends Request> type
589     ) throws Exception {
590         final MkContainer container = new MkGrizzlyContainer().next(
591             new MkAnswer.Simple("")
592         ).start();
593         final URI uri = UriBuilder.fromUri(container.home())
594             .userInfo("user:\u20ac\u20ac").build();
595         RequestTestTemplate.request(uri, type)
596             .through(BasicAuthWire.class)
597             .method(Request.GET)
598             .uri().path("/abcde").back()
599             .fetch().as(RestResponse.class)
600             .assertStatus(HttpURLConnection.HTTP_OK);
601         container.stop();
602         final MkQuery query = container.take();
603         MatcherAssert.assertThat(
604             query.headers(),
605             Matchers.hasEntry(
606                 Matchers.equalTo(HttpHeaders.AUTHORIZATION),
607                 Matchers.hasItem("Basic dXNlcjrigqzigqw=")
608             )
609         );
610     }
611 
612     /**
613      * BaseRequest can fetch GET request twice.
614      * @throws Exception If something goes wrong inside
615      * @param type Request type
616      */
617     @Values
618     @ParameterizedTest
619     void sendsIdenticalHttpRequestTwice(
620         final Class<? extends Request> type
621     ) throws Exception {
622         final MkContainer container = new MkGrizzlyContainer()
623             .next(new MkAnswer.Simple(""))
624             .next(new MkAnswer.Simple(""))
625             .next(new MkAnswer.Simple(""))
626             .start();
627         final Request req = RequestTestTemplate.request(container.home(), type)
628             .uri().path("/foo-X").back()
629             .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_XML);
630         req.method(Request.GET).fetch().as(RestResponse.class)
631             .assertStatus(HttpURLConnection.HTTP_OK);
632         req.method(Request.POST).fetch().as(RestResponse.class)
633             .assertStatus(HttpURLConnection.HTTP_OK);
634         req.method(Request.GET).fetch().as(RestResponse.class)
635             .assertStatus(HttpURLConnection.HTTP_OK);
636         container.stop();
637         MatcherAssert.assertThat(
638             container.take(),
639             MkQueryMatchers.hasPath(Matchers.endsWith("foo-X"))
640         );
641     }
642 
643     /**
644      * BaseRequest can return redirect status (without redirecting).
645      * @throws Exception If something goes wrong inside
646      * @since 0.10
647      * @param type Request type
648      */
649     @Values
650     @ParameterizedTest
651     void doesntRedirectWithoutRequest(
652         final Class<? extends Request> type
653     ) throws Exception {
654         final MkContainer container = new MkGrizzlyContainer().next(
655             new MkAnswer.Simple("")
656                 .withStatus(HttpURLConnection.HTTP_SEE_OTHER)
657                 .withHeader(HttpHeaders.LOCATION, "http://www.google.com")
658         ).start();
659         RequestTestTemplate.request(container.home(), type)
660             .fetch().as(RestResponse.class)
661             .assertStatus(HttpURLConnection.HTTP_SEE_OTHER);
662         container.stop();
663     }
664 
665     /**
666      * BaseRequest can fetch body with HTTP POST request.
667      * @throws Exception If something goes wrong inside
668      * @param type Request type
669      */
670     @Values
671     @ParameterizedTest
672     void sendsRequestBodyAsInputStream(
673         final Class<? extends Request> type
674     ) throws Exception {
675         final MkContainer container = new MkGrizzlyContainer().next(
676             new MkAnswer.Simple("")
677         ).start();
678         final String value = "\u20ac body as stream \"&^%*;'\"";
679         final ByteArrayInputStream stream =
680             new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8));
681         RequestTestTemplate.request(container.home(), type)
682             .method(Request.POST)
683             .header(
684                 HttpHeaders.CONTENT_TYPE,
685                 MediaType.APPLICATION_FORM_URLENCODED
686             )
687             .fetch(stream).as(RestResponse.class)
688             .assertStatus(HttpURLConnection.HTTP_OK);
689         final MkQuery query = container.take();
690         MatcherAssert.assertThat(
691             query.body(),
692             Matchers.containsString(value)
693         );
694         container.stop();
695     }
696 
697     /**
698      * BaseRequest.fetch(InputStream) throws an exception if the body has been
699      * previously set.
700      * @param type Request type
701      */
702     @Values
703     @ParameterizedTest
704     void fetchThrowsExceptionWhenBodyIsNotEmpty(
705         final Class<? extends Request> type
706     ) {
707         Assertions.assertThrows(
708             IllegalStateException.class,
709             new Executable() {
710                 @Override
711                 public void execute() throws Throwable {
712                     RequestTestTemplate.request(
713                         new URI("http://localhost:78787"),
714                         type
715                     )
716                         .method(Request.GET)
717                         .body().set("already set").back()
718                         .fetch(
719                             new ByteArrayInputStream(
720                                 "ba".getBytes(StandardCharsets.UTF_8)
721                             )
722                         );
723                 }
724             }
725         );
726     }
727 
728     /**
729      * Content type stream.
730      * @return Content type header.
731      */
732     private static String steamContentType() {
733         return "Content-Type: application/octet-stream";
734     }
735 }