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.response;
31  
32  import com.fasterxml.jackson.databind.node.ArrayNode;
33  import com.fasterxml.jackson.databind.node.ObjectNode;
34  import com.jcabi.http.request.FakeRequest;
35  import java.io.IOException;
36  import org.hamcrest.MatcherAssert;
37  import org.hamcrest.Matchers;
38  import org.junit.jupiter.api.Assertions;
39  import org.junit.jupiter.api.Test;
40  import org.junit.jupiter.api.function.Executable;
41  
42  /**
43   * Test case for {@link JacksonResponse}.
44   *
45   * @since 1.17
46   */
47  final class JacksonResponseTest {
48      /**
49       * JacksonResponse can read and return a JSON document.
50       *
51       * @throws IOException If anything goes wrong when parsing.
52       */
53      @Test
54      void canReadJsonDocument() throws IOException {
55          final JacksonResponse response = new FakeRequest()
56              .withBody("{\n\t\r\"foo-foo\":2,\n\"bar\":\"\u20ac\"}")
57              .fetch().as(JacksonResponse.class);
58          MatcherAssert.assertThat(
59              response.json().read().path("foo-foo").asInt(),
60              Matchers.equalTo(2)
61          );
62          MatcherAssert.assertThat(
63              response.json().read().path("bar").asText(),
64              Matchers.equalTo("\u20ac")
65          );
66      }
67  
68      /**
69       * JacksonResponse can read control characters.
70       *
71       * @throws IOException If anything goes wrong when parsing.
72       */
73      @Test
74      void canParseUnquotedControlCharacters() throws IOException {
75          final JacksonResponse response = new FakeRequest()
76              .withBody("{\"test\":\n\"\u001Fblah\uFFFDcwhoa\u0000!\"}")
77              .fetch().as(JacksonResponse.class);
78          MatcherAssert.assertThat(
79              response.json().readObject().get("test").asText(),
80              Matchers.is("\u001Fblah\uFFFDcwhoa\u0000!")
81          );
82      }
83  
84      /**
85       * If there's a problem parsing the body as JSON the error handling is done
86       * by Jackson.
87       *
88       * @throws IOException If anything goes wrong.
89       */
90      @Test
91      void invalidJsonErrorHandlingIsLeftToJackson() throws IOException {
92          final String body = "{test:[]}";
93          final String err = "was expecting double-quote to start field name";
94          final JacksonResponse response = new FakeRequest()
95              .withBody(body).fetch().as(JacksonResponse.class);
96          MatcherAssert.assertThat(
97              Assertions.assertThrows(
98                  IOException.class,
99                  new Executable() {
100                     @Override
101                     public void execute() throws Throwable {
102                         response.json().read();
103                     }
104                 }
105             ),
106             Matchers.hasProperty(
107                 "message",
108                 Matchers.containsString(err)
109             )
110         );
111     }
112 
113     /**
114      * If there's a problem parsing the body as JSON the error handling is done
115      * by Jackson.
116      *
117      * @throws IOException If anything goes wrong.
118      */
119     @Test
120     void invalidJsonArrayErrorHandlingIsLeftToJackson()
121         throws IOException {
122         final JacksonResponse response = new FakeRequest()
123             .withBody("{\"anInvalidArrayTest\":[}")
124             .fetch().as(JacksonResponse.class);
125         MatcherAssert.assertThat(
126             Assertions.assertThrows(
127                 IOException.class,
128                 new Executable() {
129                     @Override
130                     public void execute() throws Throwable {
131                         response.json().readArray();
132                     }
133                 }
134             ),
135             Matchers.hasToString(
136                 Matchers.containsString("Unexpected close marker")
137             )
138         );
139     }
140 
141     /**
142      * If the parsed JSON is a valid one but an array an exception is raised.
143      *
144      * @throws IOException If anything goes wrong.
145      */
146     @Test
147     void cannotReadJsonAsArrayIfNotOne() throws IOException {
148         final JacksonResponse response = new FakeRequest()
149             .withBody("{\"objectIsNotArray\": \"It's not!\"}")
150             .fetch().as(JacksonResponse.class);
151         MatcherAssert.assertThat(
152             Assertions.assertThrows(
153                 IOException.class,
154                 new Executable() {
155                     @Override
156                     public void execute() throws Throwable {
157                         response.json().readArray();
158                     }
159                 }
160             ),
161             Matchers.<IOException>hasToString(
162                 Matchers.containsString(
163                     "Cannot read as an array. The JSON is not a valid array."
164                 )
165             )
166         );
167     }
168 
169     /**
170      * Can retrieve the JSON as an array node if it's a valid one.
171      *
172      * @throws IOException If anything goes wrong.
173      */
174     @Test
175     void canReadAsArrayIfOne() throws IOException {
176         final JacksonResponse response = new FakeRequest()
177             .withBody("[\"one\", \"two\"]")
178             .fetch().as(JacksonResponse.class);
179         final ArrayNode array = response.json().readArray();
180         MatcherAssert.assertThat(
181             array.get(0).asText(), Matchers.is("one")
182         );
183         MatcherAssert.assertThat(
184             array.get(1).asText(), Matchers.is("two")
185         );
186     }
187 
188     /**
189      * If there's a problem parsing the body as JSON the error handling is done
190      * by Jackson.
191      *
192      * @throws IOException If anything goes wrong.
193      */
194     @Test
195     void invalidJsonObjectErrorIsLeftToJackson() throws IOException {
196         final JacksonResponse response = new FakeRequest()
197             .withBody("{\"anInvalidObjectTest\":{}")
198             .fetch().as(JacksonResponse.class);
199         MatcherAssert.assertThat(
200             Assertions.assertThrows(
201                 IOException.class,
202                 new Executable() {
203                     @Override
204                     public void execute() throws Throwable {
205                         response.json().readObject();
206                     }
207                 }
208             ),
209             Matchers.<IOException>hasToString(
210                 Matchers.containsString(
211                     "Unexpected end-of-input: expected close marker for Object"
212                 )
213             )
214         );
215     }
216 
217     /**
218      * If the parsed JSON is a valid one but an object an exception is raised.
219      *
220      * @throws IOException If anything goes wrong.
221      */
222     @Test
223     void cannotReadJsonAsObjectIfNotOne() throws IOException {
224         final String body = "[\"arrayIsNotObject\", \"It's not!\"]";
225         final JacksonResponse response = new FakeRequest()
226             .withBody(body).fetch().as(JacksonResponse.class);
227         MatcherAssert.assertThat(
228             Assertions.assertThrows(
229                 IOException.class,
230                 new Executable() {
231                     @Override
232                     public void execute() throws Throwable {
233                         response.json().readObject();
234                     }
235                 }
236             ),
237             Matchers.<IOException>hasToString(
238                 Matchers.containsString(
239                     "Cannot read as an object. The JSON is not a valid object."
240                 )
241             )
242         );
243     }
244 
245     /**
246      * Can retrieve the JSON as an object node if it's a valid one.
247      *
248      * @throws IOException If anything goes wrong.
249      */
250     @Test
251     void canReadAsObjectIfOne() throws IOException {
252         final JacksonResponse response = new FakeRequest()
253             .withBody("{\"hooray\": \"Got milk?\"}")
254             .fetch().as(JacksonResponse.class);
255         final ObjectNode object = response.json().readObject();
256         MatcherAssert.assertThat(
257             object.get("hooray").asText(), Matchers.is("Got milk?")
258         );
259     }
260 }