001/* 002 * acme4j - Java ACME client 003 * 004 * Copyright (C) 2017 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; 015 016import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; 017import static org.assertj.core.api.Assertions.assertThat; 018import static org.shredzone.acme4j.toolbox.TestUtils.getJSON; 019import static org.shredzone.acme4j.toolbox.TestUtils.url; 020 021import java.net.HttpURLConnection; 022import java.net.URI; 023import java.net.URL; 024import java.time.Duration; 025import java.util.concurrent.atomic.AtomicBoolean; 026 027import org.assertj.core.api.AutoCloseableSoftAssertions; 028import org.junit.jupiter.api.Test; 029import org.shredzone.acme4j.exception.AcmeNotSupportedException; 030import org.shredzone.acme4j.provider.TestableConnectionProvider; 031import org.shredzone.acme4j.toolbox.JSON; 032import org.shredzone.acme4j.toolbox.JSONBuilder; 033import org.shredzone.acme4j.toolbox.TestUtils; 034 035/** 036 * Unit tests for {@link Order}. 037 */ 038public class OrderTest { 039 040 private final URL locationUrl = url("http://example.com/acme/order/1234"); 041 private final URL finalizeUrl = url("https://example.com/acme/acct/1/order/1/finalize"); 042 043 /** 044 * Test that order is properly updated. 045 */ 046 @Test 047 public void testUpdate() throws Exception { 048 var provider = new TestableConnectionProvider() { 049 @Override 050 public int sendSignedPostAsGetRequest(URL url, Login login) { 051 assertThat(url).isEqualTo(locationUrl); 052 return HttpURLConnection.HTTP_OK; 053 } 054 055 @Override 056 public JSON readJsonResponse() { 057 return getJSON("updateOrderResponse"); 058 } 059 }; 060 061 var login = provider.createLogin(); 062 063 var order = new Order(login, locationUrl); 064 order.update(); 065 066 try (var softly = new AutoCloseableSoftAssertions()) { 067 softly.assertThat(order.getStatus()).isEqualTo(Status.PENDING); 068 softly.assertThat(order.getExpires().orElseThrow()).isEqualTo("2015-03-01T14:09:00Z"); 069 softly.assertThat(order.getLocation()).isEqualTo(locationUrl); 070 071 softly.assertThat(order.getIdentifiers()).containsExactlyInAnyOrder( 072 Identifier.dns("example.com"), 073 Identifier.dns("www.example.com")); 074 softly.assertThat(order.getNotBefore().orElseThrow()) 075 .isEqualTo("2016-01-01T00:00:00Z"); 076 softly.assertThat(order.getNotAfter().orElseThrow()) 077 .isEqualTo("2016-01-08T00:00:00Z"); 078 softly.assertThat(order.getCertificate().getLocation()) 079 .isEqualTo(url("https://example.com/acme/cert/1234")); 080 softly.assertThat(order.getFinalizeLocation()).isEqualTo(finalizeUrl); 081 082 softly.assertThat(order.isAutoRenewing()).isFalse(); 083 softly.assertThatExceptionOfType(AcmeNotSupportedException.class) 084 .isThrownBy(order::getAutoRenewalStartDate); 085 softly.assertThatExceptionOfType(AcmeNotSupportedException.class) 086 .isThrownBy(order::getAutoRenewalEndDate); 087 softly.assertThatExceptionOfType(AcmeNotSupportedException.class) 088 .isThrownBy(order::getAutoRenewalLifetime); 089 softly.assertThatExceptionOfType(AcmeNotSupportedException.class) 090 .isThrownBy(order::getAutoRenewalLifetimeAdjust); 091 softly.assertThatExceptionOfType(AcmeNotSupportedException.class) 092 .isThrownBy(order::isAutoRenewalGetEnabled); 093 094 softly.assertThat(order.getError()).isNotEmpty(); 095 softly.assertThat(order.getError().orElseThrow().getType()) 096 .isEqualTo(URI.create("urn:ietf:params:acme:error:connection")); 097 softly.assertThat(order.getError().flatMap(Problem::getDetail).orElseThrow()) 098 .isEqualTo("connection refused"); 099 100 var auths = order.getAuthorizations(); 101 softly.assertThat(auths).hasSize(2); 102 softly.assertThat(auths.stream()) 103 .map(Authorization::getLocation) 104 .containsExactlyInAnyOrder( 105 url("https://example.com/acme/authz/1234"), 106 url("https://example.com/acme/authz/2345")); 107 } 108 109 provider.close(); 110 } 111 112 /** 113 * Test lazy loading. 114 */ 115 @Test 116 public void testLazyLoading() throws Exception { 117 var requestWasSent = new AtomicBoolean(false); 118 119 var provider = new TestableConnectionProvider() { 120 @Override 121 public int sendSignedPostAsGetRequest(URL url, Login login) { 122 requestWasSent.set(true); 123 assertThat(url).isEqualTo(locationUrl); 124 return HttpURLConnection.HTTP_OK; 125 } 126 127 @Override 128 public JSON readJsonResponse() { 129 return getJSON("updateOrderResponse"); 130 } 131 }; 132 133 var login = provider.createLogin(); 134 135 var order = new Order(login, locationUrl); 136 137 try (var softly = new AutoCloseableSoftAssertions()) { 138 // Lazy loading 139 softly.assertThat(requestWasSent).isFalse(); 140 softly.assertThat(order.getCertificate().getLocation()) 141 .isEqualTo(url("https://example.com/acme/cert/1234")); 142 softly.assertThat(requestWasSent).isTrue(); 143 144 // Subsequent queries do not trigger another load 145 requestWasSent.set(false); 146 softly.assertThat(order.getCertificate().getLocation()) 147 .isEqualTo(url("https://example.com/acme/cert/1234")); 148 softly.assertThat(order.getStatus()).isEqualTo(Status.PENDING); 149 softly.assertThat(order.getExpires().orElseThrow()).isEqualTo("2015-03-01T14:09:00Z"); 150 softly.assertThat(requestWasSent).isFalse(); 151 } 152 153 provider.close(); 154 } 155 156 /** 157 * Test that order is properly finalized. 158 */ 159 @Test 160 public void testFinalize() throws Exception { 161 var csr = TestUtils.getResourceAsByteArray("/csr.der"); 162 163 var provider = new TestableConnectionProvider() { 164 private boolean isFinalized = false; 165 166 @Override 167 public int sendSignedPostAsGetRequest(URL url, Login login) { 168 assertThat(url).isEqualTo(locationUrl); 169 return HttpURLConnection.HTTP_OK; 170 } 171 172 @Override 173 public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { 174 assertThat(url).isEqualTo(finalizeUrl); 175 assertThatJson(claims.toString()).isEqualTo(getJSON("finalizeRequest").toString()); 176 assertThat(login).isNotNull(); 177 isFinalized = true; 178 return HttpURLConnection.HTTP_OK; 179 } 180 181 @Override 182 public JSON readJsonResponse() { 183 return getJSON(isFinalized ? "finalizeResponse" : "updateOrderResponse"); 184 } 185 }; 186 187 var login = provider.createLogin(); 188 189 var order = new Order(login, locationUrl); 190 order.execute(csr); 191 192 try (var softly = new AutoCloseableSoftAssertions()) { 193 softly.assertThat(order.getStatus()).isEqualTo(Status.VALID); 194 softly.assertThat(order.getExpires().orElseThrow()).isEqualTo("2015-03-01T14:09:00Z"); 195 softly.assertThat(order.getLocation()).isEqualTo(locationUrl); 196 197 softly.assertThat(order.getIdentifiers()).containsExactlyInAnyOrder( 198 Identifier.dns("example.com"), 199 Identifier.dns("www.example.com")); 200 softly.assertThat(order.getNotBefore().orElseThrow()) 201 .isEqualTo("2016-01-01T00:00:00Z"); 202 softly.assertThat(order.getNotAfter().orElseThrow()) 203 .isEqualTo("2016-01-08T00:00:00Z"); 204 softly.assertThat(order.getCertificate().getLocation()) 205 .isEqualTo(url("https://example.com/acme/cert/1234")); 206 softly.assertThatIllegalStateException() 207 .isThrownBy(order::getAutoRenewalCertificate); 208 softly.assertThat(order.getFinalizeLocation()).isEqualTo(finalizeUrl); 209 210 var auths = order.getAuthorizations(); 211 softly.assertThat(auths).hasSize(2); 212 softly.assertThat(auths.stream()) 213 .map(Authorization::getLocation) 214 .containsExactlyInAnyOrder( 215 url("https://example.com/acme/authz/1234"), 216 url("https://example.com/acme/authz/2345")); 217 } 218 219 provider.close(); 220 } 221 222 /** 223 * Test that order is properly updated. 224 */ 225 @Test 226 public void testAutoRenewUpdate() throws Exception { 227 var provider = new TestableConnectionProvider() { 228 @Override 229 public int sendSignedPostAsGetRequest(URL url, Login login) { 230 assertThat(url).isEqualTo(locationUrl); 231 return HttpURLConnection.HTTP_OK; 232 } 233 234 @Override 235 public JSON readJsonResponse() { 236 return getJSON("updateAutoRenewOrderResponse"); 237 } 238 }; 239 240 provider.putMetadata("auto-renewal", JSON.empty()); 241 242 var login = provider.createLogin(); 243 244 var order = new Order(login, locationUrl); 245 order.update(); 246 247 try (var softly = new AutoCloseableSoftAssertions()) { 248 softly.assertThat(order.isAutoRenewing()).isTrue(); 249 softly.assertThat(order.getAutoRenewalStartDate().orElseThrow()) 250 .isEqualTo("2016-01-01T00:00:00Z"); 251 softly.assertThat(order.getAutoRenewalEndDate()) 252 .isEqualTo("2017-01-01T00:00:00Z"); 253 softly.assertThat(order.getAutoRenewalLifetime()) 254 .isEqualTo(Duration.ofHours(168)); 255 softly.assertThat(order.getAutoRenewalLifetimeAdjust().orElseThrow()) 256 .isEqualTo(Duration.ofDays(6)); 257 softly.assertThat(order.getNotBefore()).isEmpty(); 258 softly.assertThat(order.getNotAfter()).isEmpty(); 259 softly.assertThat(order.isAutoRenewalGetEnabled()).isTrue(); 260 } 261 262 provider.close(); 263 } 264 265 /** 266 * Test that auto-renew order is properly finalized. 267 */ 268 @Test 269 public void testAutoRenewFinalize() throws Exception { 270 var provider = new TestableConnectionProvider() { 271 @Override 272 public int sendSignedPostAsGetRequest(URL url, Login login) { 273 assertThat(url).isEqualTo(locationUrl); 274 return HttpURLConnection.HTTP_OK; 275 } 276 277 @Override 278 public JSON readJsonResponse() { 279 return getJSON("finalizeAutoRenewResponse"); 280 } 281 }; 282 283 var login = provider.createLogin(); 284 var order = login.bindOrder(locationUrl); 285 286 try (var softly = new AutoCloseableSoftAssertions()) { 287 softly.assertThatIllegalStateException() 288 .isThrownBy(order::getCertificate); 289 softly.assertThat(order.getAutoRenewalCertificate().getLocation()) 290 .isEqualTo(url("https://example.com/acme/cert/1234")); 291 softly.assertThat(order.isAutoRenewing()).isTrue(); 292 softly.assertThat(order.getAutoRenewalStartDate().orElseThrow()) 293 .isEqualTo("2018-01-01T00:00:00Z"); 294 softly.assertThat(order.getAutoRenewalEndDate()) 295 .isEqualTo("2019-01-01T00:00:00Z"); 296 softly.assertThat(order.getAutoRenewalLifetime()) 297 .isEqualTo(Duration.ofHours(168)); 298 softly.assertThat(order.getAutoRenewalLifetimeAdjust().orElseThrow()) 299 .isEqualTo(Duration.ofDays(6)); 300 softly.assertThat(order.getNotBefore()).isEmpty(); 301 softly.assertThat(order.getNotAfter()).isEmpty(); 302 softly.assertThat(order.isAutoRenewalGetEnabled()).isTrue(); 303 } 304 305 provider.close(); 306 } 307 308 /** 309 * Test that auto-renew order is properly canceled. 310 */ 311 @Test 312 public void testCancel() throws Exception { 313 var provider = new TestableConnectionProvider() { 314 @Override 315 public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { 316 var json = claims.toJSON(); 317 assertThat(json.get("status").asString()).isEqualTo("canceled"); 318 assertThat(url).isEqualTo(locationUrl); 319 assertThat(login).isNotNull(); 320 return HttpURLConnection.HTTP_OK; 321 } 322 323 @Override 324 public JSON readJsonResponse() { 325 return getJSON("canceledOrderResponse"); 326 } 327 }; 328 329 provider.putMetadata("auto-renewal", JSON.empty()); 330 331 var login = provider.createLogin(); 332 333 var order = new Order(login, locationUrl); 334 order.cancelAutoRenewal(); 335 336 assertThat(order.getStatus()).isEqualTo(Status.CANCELED); 337 338 provider.close(); 339 } 340 341}