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