001/*
002 * acme4j - Java ACME client
003 *
004 * Copyright (C) 2016 Richard "Shred" Körber
005 *   http://acme4j.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.acme4j.toolbox;
015
016import static java.nio.charset.StandardCharsets.UTF_8;
017import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
018import static org.assertj.core.api.Assertions.assertThat;
019import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
020import static org.junit.jupiter.api.Assertions.assertThrows;
021import static org.shredzone.acme4j.toolbox.TestUtils.url;
022
023import java.io.ByteArrayInputStream;
024import java.io.ByteArrayOutputStream;
025import java.io.IOException;
026import java.io.ObjectInputStream;
027import java.io.ObjectOutputStream;
028import java.net.URI;
029import java.net.URL;
030import java.time.LocalDate;
031import java.time.ZoneId;
032import java.util.ArrayList;
033import java.util.NoSuchElementException;
034import java.util.stream.Collectors;
035
036import org.junit.jupiter.api.Test;
037import org.shredzone.acme4j.Status;
038import org.shredzone.acme4j.exception.AcmeNotSupportedException;
039import org.shredzone.acme4j.exception.AcmeProtocolException;
040import org.shredzone.acme4j.toolbox.JSON.Value;
041
042/**
043 * Unit test for {@link JSON}.
044 */
045public class JSONTest {
046
047    private static final URL BASE_URL = url("https://example.com/acme/1");
048
049    /**
050     * Test that an empty {@link JSON} is empty.
051     */
052    @Test
053    public void testEmpty() {
054        var empty = JSON.empty();
055        assertThat(empty.toString()).isEqualTo("{}");
056        assertThat(empty.toMap().keySet()).isEmpty();
057    }
058
059    /**
060     * Test parsers.
061     */
062    @Test
063    public void testParsers() throws IOException {
064        String json = "{\"foo\":\"a-text\",\n\"bar\":123}";
065
066        var fromString = JSON.parse(json);
067        assertThatJson(fromString.toString()).isEqualTo(json);
068        var map = fromString.toMap();
069        assertThat(map).hasSize(2);
070        assertThat(map.keySet()).containsExactlyInAnyOrder("foo", "bar");
071        assertThat(map.get("foo")).isEqualTo("a-text");
072        assertThat(map.get("bar")).isEqualTo(123L);
073
074        try (var in = new ByteArrayInputStream(json.getBytes(UTF_8))) {
075            var fromStream = JSON.parse(in);
076            assertThatJson(fromStream.toString()).isEqualTo(json);
077            var map2 = fromStream.toMap();
078            assertThat(map2).hasSize(2);
079            assertThat(map2.keySet()).containsExactlyInAnyOrder("foo", "bar");
080            assertThat(map2.get("foo")).isEqualTo("a-text");
081            assertThat(map2.get("bar")).isEqualTo(123L);
082        }
083    }
084
085    /**
086     * Test that bad JSON fails.
087     */
088    @Test
089    public void testParsersBadJSON() {
090        assertThrows(AcmeProtocolException.class,
091                () -> JSON.parse("This is no JSON.")
092        );
093    }
094
095    /**
096     * Test all object related methods.
097     */
098    @Test
099    public void testObject() {
100        var json = TestUtils.getJSON("datatypes");
101
102        assertThat(json.keySet()).containsExactlyInAnyOrder(
103                    "text", "number", "boolean", "uri", "url", "date", "array",
104                    "collect", "status", "binary", "duration", "problem", "encoded");
105        assertThat(json.contains("text")).isTrue();
106        assertThat(json.contains("music")).isFalse();
107        assertThat(json.get("text")).isNotNull();
108        assertThat(json.get("music")).isNotNull();
109    }
110
111    /**
112     * Test all array related methods.
113     */
114    @Test
115    public void testArray() {
116        var json = TestUtils.getJSON("datatypes");
117        var array = json.get("array").asArray();
118
119        assertThat(array.isEmpty()).isFalse();
120        assertThat(array).hasSize(4).doesNotContainNull();
121    }
122
123    /**
124     * Test empty array.
125     */
126    @Test
127    public void testEmptyArray() {
128        var json = TestUtils.getJSON("datatypes");
129        var array = json.get("missingArray").asArray();
130
131        assertThat(array.isEmpty()).isTrue();
132        assertThat(array).hasSize(0);
133        assertThat(array.stream().count()).isEqualTo(0L);
134    }
135
136    /**
137     * Test all array iterator related methods.
138     */
139    @Test
140    public void testArrayIterator() {
141        var json = TestUtils.getJSON("datatypes");
142        var array = json.get("array").asArray();
143
144        var it = array.iterator();
145        assertThat(it).isNotNull();
146
147        assertThat(it.hasNext()).isTrue();
148        assertThat(it.next().asString()).isEqualTo("foo");
149
150        assertThat(it.hasNext()).isTrue();
151        assertThat(it.next().asInt()).isEqualTo(987);
152
153        assertThat(it.hasNext()).isTrue();
154        assertThat(it.next().asArray()).hasSize(3);
155
156        assertThat(it.hasNext()).isTrue();
157        assertThrows(UnsupportedOperationException.class, it::remove);
158        assertThat(it.next().asObject()).isNotNull();
159
160        assertThat(it.hasNext()).isFalse();
161        assertThrows(NoSuchElementException.class, it::next);
162    }
163
164    /**
165     * Test the array stream.
166     */
167    @Test
168    public void testArrayStream() {
169        var json = TestUtils.getJSON("datatypes");
170        var array = json.get("array").asArray();
171
172        var streamValues = array.stream().collect(Collectors.toList());
173
174        var iteratorValues = new ArrayList<JSON.Value>();
175        for (var value : array) {
176            iteratorValues.add(value);
177        }
178
179        assertThat(streamValues).containsAll(iteratorValues);
180    }
181
182    /**
183     * Test all getters on existing values.
184     */
185    @Test
186    public void testGetter() {
187        var date = LocalDate.of(2016, 1, 8).atStartOfDay(ZoneId.of("UTC")).toInstant();
188
189        var json = TestUtils.getJSON("datatypes");
190
191        assertThat(json.get("text").asString()).isEqualTo("lorem ipsum");
192        assertThat(json.get("number").asInt()).isEqualTo(123);
193        assertThat(json.get("boolean").asBoolean()).isTrue();
194        assertThat(json.get("uri").asURI()).isEqualTo(URI.create("mailto:foo@example.com"));
195        assertThat(json.get("url").asURL()).isEqualTo(url("http://example.com"));
196        assertThat(json.get("date").asInstant()).isEqualTo(date);
197        assertThat(json.get("status").asStatus()).isEqualTo(Status.VALID);
198        assertThat(json.get("binary").asBinary()).isEqualTo("Chainsaw".getBytes());
199        assertThat(json.get("duration").asDuration()).hasSeconds(86400L);
200
201        assertThat(json.get("text").isPresent()).isTrue();
202        assertThat(json.get("text").optional().isPresent()).isTrue();
203        assertThat(json.get("text").map(Value::asString).isPresent()).isTrue();
204
205        var array = json.get("array").asArray();
206        assertThat(array.get(0).asString()).isEqualTo("foo");
207        assertThat(array.get(1).asInt()).isEqualTo(987);
208
209        var array2 = array.get(2).asArray();
210        assertThat(array2.get(0).asInt()).isEqualTo(1);
211        assertThat(array2.get(1).asInt()).isEqualTo(2);
212        assertThat(array2.get(2).asInt()).isEqualTo(3);
213
214        var sub = array.get(3).asObject();
215        assertThat(sub.get("test").asString()).isEqualTo("ok");
216
217        var encodedSub = json.get("encoded").asEncodedObject();
218        assertThatJson(encodedSub.toString()).isEqualTo("{\"key\":\"value\"}");
219
220        var problem = json.get("problem").asProblem(BASE_URL);
221        assertThat(problem).isNotNull();
222        assertThat(problem.getType()).isEqualTo(URI.create("urn:ietf:params:acme:error:rateLimited"));
223        assertThat(problem.getDetail().orElseThrow()).isEqualTo("too many requests");
224        assertThat(problem.getInstance().orElseThrow())
225                .isEqualTo(URI.create("https://example.com/documents/errors.html"));
226    }
227
228    /**
229     * Test that getters are null safe.
230     */
231    @Test
232    public void testNullGetter() {
233        var json = TestUtils.getJSON("datatypes");
234
235        assertThat(json.get("none")).isNotNull();
236        assertThat(json.get("none").isPresent()).isFalse();
237        assertThat(json.get("none").optional().isPresent()).isFalse();
238        assertThat(json.get("none").map(Value::asString).isPresent()).isFalse();
239
240        assertThatExceptionOfType(AcmeNotSupportedException.class)
241                .isThrownBy(() -> json.getFeature("none"))
242                .withMessage("Server does not support none");
243
244        assertThatExceptionOfType(AcmeNotSupportedException.class)
245                .isThrownBy(() -> json.get("none").onFeature("my-feature"))
246                .withMessage("Server does not support my-feature");
247
248        assertThrows(AcmeProtocolException.class,
249                () -> json.get("none").asString(),
250                "asString");
251        assertThrows(AcmeProtocolException.class,
252                () -> json.get("none").asURI(),
253                "asURI");
254        assertThrows(AcmeProtocolException.class,
255                () -> json.get("none").asURL(),
256                "asURL");
257        assertThrows(AcmeProtocolException.class,
258                () -> json.get("none").asInstant(),
259                "asInstant");
260        assertThrows(AcmeProtocolException.class,
261                () -> json.get("none").asDuration(),
262                "asDuration");
263        assertThrows(AcmeProtocolException.class,
264                () -> json.get("none").asObject(),
265                "asObject");
266        assertThrows(AcmeProtocolException.class,
267                () -> json.get("none").asEncodedObject(),
268                "asEncodedObject");
269        assertThrows(AcmeProtocolException.class,
270                () -> json.get("none").asStatus(),
271                "asStatus");
272        assertThrows(AcmeProtocolException.class,
273                () -> json.get("none").asBinary(),
274                "asBinary");
275        assertThrows(AcmeProtocolException.class,
276                () -> json.get("none").asProblem(BASE_URL),
277                "asProblem");
278        assertThrows(AcmeProtocolException.class,
279                () -> json.get("none").asInt(),
280                "asInt");
281        assertThrows(AcmeProtocolException.class,
282                () -> json.get("none").asBoolean(),
283                "asBoolean");
284    }
285
286    /**
287     * Test that wrong getters return an exception.
288     */
289    @Test
290    public void testWrongGetter() {
291        var json = TestUtils.getJSON("datatypes");
292
293        assertThrows(AcmeProtocolException.class,
294                () -> json.get("text").asObject(),
295                "asObject");
296        assertThrows(AcmeProtocolException.class,
297                () -> json.get("text").asEncodedObject(),
298                "asEncodedObject");
299        assertThrows(AcmeProtocolException.class,
300                () -> json.get("text").asArray(),
301                "asArray");
302        assertThrows(AcmeProtocolException.class,
303                () -> json.get("text").asInt(),
304                "asInt");
305        assertThrows(AcmeProtocolException.class,
306                () -> json.get("text").asURI(),
307                "asURI");
308        assertThrows(AcmeProtocolException.class,
309                () -> json.get("text").asURL(),
310                "asURL");
311        assertThrows(AcmeProtocolException.class,
312                () -> json.get("text").asInstant(),
313                "asInstant");
314        assertThrows(AcmeProtocolException.class,
315                () -> json.get("text").asDuration(),
316                "asDuration");
317        assertThrows(AcmeProtocolException.class,
318                () -> json.get("text").asProblem(BASE_URL),
319                "asProblem");
320    }
321
322    /**
323     * Test that serialization works correctly.
324     */
325    @Test
326    public void testSerialization() throws IOException, ClassNotFoundException {
327        var originalJson = TestUtils.getJSON("newAuthorizationResponse");
328
329        // Serialize
330        byte[] data;
331        try (var out = new ByteArrayOutputStream()) {
332            try (var oos = new ObjectOutputStream(out)) {
333                oos.writeObject(originalJson);
334            }
335            data = out.toByteArray();
336        }
337
338        // Deserialize
339        JSON testJson;
340        try (var in = new ByteArrayInputStream(data)) {
341            try (var ois = new ObjectInputStream(in)) {
342                testJson = (JSON) ois.readObject();
343            }
344        }
345
346        assertThat(testJson).isNotSameAs(originalJson);
347        assertThat(testJson.toString()).isNotEmpty();
348        assertThatJson(testJson.toString()).isEqualTo(originalJson.toString());
349    }
350
351}