001/* 002 * acme4j - Java ACME client 003 * 004 * Copyright (C) 2023 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 org.assertj.core.api.Assertions.assertThat; 017import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 018import static org.mockito.Mockito.mock; 019import static org.shredzone.acme4j.toolbox.TestUtils.getJSON; 020import static org.shredzone.acme4j.toolbox.TestUtils.url; 021 022import java.net.HttpURLConnection; 023import java.net.URL; 024import java.time.Duration; 025import java.time.Instant; 026import java.time.ZonedDateTime; 027import java.time.temporal.ChronoUnit; 028import java.util.Optional; 029 030import org.assertj.core.api.AutoCloseableSoftAssertions; 031import org.junit.jupiter.api.Test; 032import org.shredzone.acme4j.exception.AcmeProtocolException; 033import org.shredzone.acme4j.provider.TestableConnectionProvider; 034import org.shredzone.acme4j.toolbox.JSON; 035 036/** 037 * Unit test for {@link RenewalInfo}. 038 */ 039public class RenewalInfoTest { 040 041 private final URL locationUrl = url("http://example.com/acme/renewalInfo/1234"); 042 private final Instant retryAfterInstant = Instant.now().plus(10L, ChronoUnit.DAYS); 043 private final Instant startWindow = Instant.parse("2021-01-03T00:00:00Z"); 044 private final Instant endWindow = Instant.parse("2021-01-07T00:00:00Z"); 045 046 @Test 047 public void testGetters() throws Exception { 048 var provider = new TestableConnectionProvider() { 049 @Override 050 public int sendRequest(URL url, Session session, ZonedDateTime ifModifiedSince) { 051 assertThat(url).isEqualTo(locationUrl); 052 assertThat(session).isNotNull(); 053 assertThat(ifModifiedSince).isNull(); 054 return HttpURLConnection.HTTP_OK; 055 } 056 057 @Override 058 public JSON readJsonResponse() { 059 return getJSON("renewalInfo"); 060 } 061 062 @Override 063 public Optional<Instant> getRetryAfter() { 064 return Optional.of(retryAfterInstant); 065 } 066 }; 067 068 var login = provider.createLogin(); 069 070 var renewalInfo = new RenewalInfo(login, locationUrl); 071 var recheckAfter = renewalInfo.fetch(); 072 assertThat(recheckAfter).hasValue(retryAfterInstant); 073 074 // Check getters 075 try (var softly = new AutoCloseableSoftAssertions()) { 076 softly.assertThat(renewalInfo.getLocation()).isEqualTo(locationUrl); 077 softly.assertThat(renewalInfo.getSuggestedWindowStart()) 078 .isEqualTo(startWindow); 079 softly.assertThat(renewalInfo.getSuggestedWindowEnd()) 080 .isEqualTo(endWindow); 081 softly.assertThat(renewalInfo.getExplanation()) 082 .isNotEmpty() 083 .contains(url("https://example.com/docs/example-mass-reissuance-event")); 084 } 085 086 // Check renewalIsNotRequired 087 try (var softly = new AutoCloseableSoftAssertions()) { 088 softly.assertThat(renewalInfo.renewalIsNotRequired(startWindow.minusSeconds(1L))) 089 .isTrue(); 090 softly.assertThat(renewalInfo.renewalIsNotRequired(startWindow)) 091 .isFalse(); 092 softly.assertThat(renewalInfo.renewalIsNotRequired(endWindow.minusSeconds(1L))) 093 .isFalse(); 094 softly.assertThat(renewalInfo.renewalIsNotRequired(endWindow)) 095 .isFalse(); 096 } 097 098 // Check renewalIsRecommended 099 try (var softly = new AutoCloseableSoftAssertions()) { 100 softly.assertThat(renewalInfo.renewalIsRecommended(startWindow.minusSeconds(1L))) 101 .isFalse(); 102 softly.assertThat(renewalInfo.renewalIsRecommended(startWindow)) 103 .isTrue(); 104 softly.assertThat(renewalInfo.renewalIsRecommended(endWindow.minusSeconds(1L))) 105 .isTrue(); 106 softly.assertThat(renewalInfo.renewalIsRecommended(endWindow)) 107 .isFalse(); 108 } 109 110 // Check renewalIsOverdue 111 try (var softly = new AutoCloseableSoftAssertions()) { 112 softly.assertThat(renewalInfo.renewalIsOverdue(startWindow.minusSeconds(1L))) 113 .isFalse(); 114 softly.assertThat(renewalInfo.renewalIsOverdue(startWindow)) 115 .isFalse(); 116 softly.assertThat(renewalInfo.renewalIsOverdue(endWindow.minusSeconds(1L))) 117 .isFalse(); 118 softly.assertThat(renewalInfo.renewalIsOverdue(endWindow)) 119 .isTrue(); 120 } 121 122 // Check getRandomProposal, is empty because end window is in the past 123 var proposal = renewalInfo.getRandomProposal(null); 124 assertThat(proposal).isEmpty(); 125 126 provider.close(); 127 } 128 129 @Test 130 public void testRandomProposal() { 131 var login = mock(Login.class); 132 var start = Instant.now(); 133 var end = start.plus(1L, ChronoUnit.DAYS); 134 135 var renewalInfo = new RenewalInfo(login, locationUrl) { 136 @Override 137 public Instant getSuggestedWindowStart() { 138 return start; 139 } 140 141 @Override 142 public Instant getSuggestedWindowEnd() { 143 return end; 144 } 145 }; 146 147 var noFreq = renewalInfo.getRandomProposal(null); 148 assertThat(noFreq).isNotEmpty(); 149 assertThat(noFreq.get()).isBetween(start, end); 150 151 var oneHour = renewalInfo.getRandomProposal(Duration.ofHours(1L)); 152 assertThat(oneHour).isNotEmpty(); 153 assertThat(oneHour.get()).isBetween(start, end.minus(1L, ChronoUnit.HOURS)); 154 155 var twoDays = renewalInfo.getRandomProposal(Duration.ofDays(2L)); 156 assertThat(twoDays).isEmpty(); 157 } 158 159 @Test 160 public void testDateAssertion() { 161 var login = mock(Login.class); 162 var start = Instant.now(); 163 var end = start.minusSeconds(1L); // end before start 164 165 var renewalInfo = new RenewalInfo(login, locationUrl) { 166 @Override 167 public Instant getSuggestedWindowStart() { 168 return start; 169 } 170 171 @Override 172 public Instant getSuggestedWindowEnd() { 173 return end; 174 } 175 }; 176 177 assertThatExceptionOfType(AcmeProtocolException.class) 178 .isThrownBy(() -> renewalInfo.renewalIsRecommended(start)); 179 } 180 181}