1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
60
61
62 @SuppressWarnings(
63 {
64 "PMD.TooManyMethods",
65 "PMD.AvoidDuplicateLiterals",
66 "PMD.TestClassWithoutTestCases"
67 })
68 final class RequestTest extends RequestTestTemplate {
69
70
71
72
73
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
104
105
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
141
142
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
172
173
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
203
204
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
240
241
242
243
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
286
287
288
289
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
332
333
334
335
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
386
387
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
419
420
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
442
443
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
463
464
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
495
496
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
517
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
535
536
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
559
560
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
582
583
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
614
615
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
645
646
647
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
667
668
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
699
700
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
730
731
732 private static String steamContentType() {
733 return "Content-Type: application/octet-stream";
734 }
735 }