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}