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}