mirror of
https://github.com/google/nomulus
synced 2026-02-10 06:50:30 +00:00
Use built-in Java URL connections instead of UrlFetchService (#1535)
- Use the standard HttpsURLConnection to write/read data - Rewrite RdeReporter, Nordn*Action, and Marksdb classes and related tests to conform to the new format - Remove FakeURLFetchService and ForwardingUrlFetchService as they weren't used - Refactor UrlFetchException to UrlConnectionException - Refactor UrlFetchUtils to UrlConnectionUtils I will need to test this on Alpha. Fortunately the connections that don't require auth (e.g. TMDB downloading) should be testable.
This commit is contained in:
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.rde;
|
||||
|
||||
import static com.google.appengine.api.urlfetch.HTTPMethod.PUT;
|
||||
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.common.Cursor.CursorType.RDE_REPORT;
|
||||
@@ -29,20 +28,13 @@ import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.joda.time.Duration.standardDays;
|
||||
import static org.joda.time.Duration.standardSeconds;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPHeader;
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.ByteSource;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.model.common.Cursor;
|
||||
@@ -57,20 +49,21 @@ import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeKeyringModule;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.FakeSleeper;
|
||||
import google.registry.testing.FakeUrlConnectionService;
|
||||
import google.registry.testing.TestOfyAndSql;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.xjc.XjcXmlTransformer;
|
||||
import google.registry.xjc.rdereport.XjcRdeReportReport;
|
||||
import google.registry.xml.XmlException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
/** Unit tests for {@link RdeReportAction}. */
|
||||
@DualDatabaseTest
|
||||
@@ -89,20 +82,21 @@ public class RdeReportActionTest {
|
||||
|
||||
private final FakeResponse response = new FakeResponse();
|
||||
private final EscrowTaskRunner runner = mock(EscrowTaskRunner.class);
|
||||
private final URLFetchService urlFetchService = mock(URLFetchService.class);
|
||||
private final ArgumentCaptor<HTTPRequest> request = ArgumentCaptor.forClass(HTTPRequest.class);
|
||||
private final HTTPResponse httpResponse = mock(HTTPResponse.class);
|
||||
private final PGPPublicKey encryptKey =
|
||||
new FakeKeyringModule().get().getRdeStagingEncryptionKey();
|
||||
private final GcsUtils gcsUtils = new GcsUtils(LocalStorageHelper.getOptions());
|
||||
private final BlobId reportFile =
|
||||
BlobId.of("tub", "test_2006-06-06_full_S1_R0-report.xml.ghostryde");
|
||||
private final HttpURLConnection httpUrlConnection = mock(HttpURLConnection.class);
|
||||
private final FakeUrlConnectionService urlConnectionService =
|
||||
new FakeUrlConnectionService(httpUrlConnection);
|
||||
private final ByteArrayOutputStream connectionOutputStream = new ByteArrayOutputStream();
|
||||
|
||||
private RdeReportAction createAction() {
|
||||
RdeReporter reporter = new RdeReporter();
|
||||
reporter.reportUrlPrefix = "https://rde-report.example";
|
||||
reporter.urlFetchService = urlFetchService;
|
||||
reporter.password = "foo";
|
||||
reporter.urlConnectionService = urlConnectionService;
|
||||
reporter.retrier = new Retrier(new FakeSleeper(new FakeClock()), 3);
|
||||
RdeReportAction action = new RdeReportAction();
|
||||
action.gcsUtils = gcsUtils;
|
||||
@@ -127,6 +121,7 @@ public class RdeReportActionTest {
|
||||
Cursor.create(RDE_UPLOAD, DateTime.parse("2006-06-07TZ"), Registry.get("test")));
|
||||
gcsUtils.createFromBytes(reportFile, Ghostryde.encode(REPORT_XML.read(), encryptKey));
|
||||
tm().transact(() -> RdeRevision.saveRevision("test", DateTime.parse("2006-06-06TZ"), FULL, 0));
|
||||
when(httpUrlConnection.getOutputStream()).thenReturn(connectionOutputStream);
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
@@ -142,24 +137,22 @@ public class RdeReportActionTest {
|
||||
|
||||
@TestOfyAndSql
|
||||
void testRunWithLock() throws Exception {
|
||||
when(httpResponse.getResponseCode()).thenReturn(SC_OK);
|
||||
when(httpResponse.getContent()).thenReturn(IIRDEA_GOOD_XML.read());
|
||||
when(urlFetchService.fetch(request.capture())).thenReturn(httpResponse);
|
||||
when(httpUrlConnection.getResponseCode()).thenReturn(SC_OK);
|
||||
when(httpUrlConnection.getInputStream()).thenReturn(IIRDEA_GOOD_XML.openStream());
|
||||
createAction().runWithLock(loadRdeReportCursor());
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
|
||||
assertThat(response.getPayload()).isEqualTo("OK test 2006-06-06T00:00:00.000Z\n");
|
||||
|
||||
// Verify the HTTP request was correct.
|
||||
assertThat(request.getValue().getMethod()).isSameInstanceAs(PUT);
|
||||
assertThat(request.getValue().getURL().getProtocol()).isEqualTo("https");
|
||||
assertThat(request.getValue().getURL().getPath()).endsWith("/test/20101017001");
|
||||
Map<String, String> headers = mapifyHeaders(request.getValue().getHeaders());
|
||||
assertThat(headers).containsEntry("CONTENT_TYPE", "text/xml");
|
||||
assertThat(headers).containsEntry("AUTHORIZATION", "Basic dGVzdF9yeTpmb28=");
|
||||
verify(httpUrlConnection).setRequestMethod("PUT");
|
||||
assertThat(httpUrlConnection.getURL().getProtocol()).isEqualTo("https");
|
||||
assertThat(httpUrlConnection.getURL().getPath()).endsWith("/test/20101017001");
|
||||
verify(httpUrlConnection).setRequestProperty("Content-Type", "text/xml; charset=utf-8");
|
||||
verify(httpUrlConnection).setRequestProperty("Authorization", "Basic dGVzdF9yeTpmb28=");
|
||||
|
||||
// Verify the payload XML was the same as what's in testdata/report.xml.
|
||||
XjcRdeReportReport report = parseReport(request.getValue().getPayload());
|
||||
XjcRdeReportReport report = parseReport(connectionOutputStream.toByteArray());
|
||||
assertThat(report.getId()).isEqualTo("20101017001");
|
||||
assertThat(report.getCrDate()).isEqualTo(DateTime.parse("2010-10-17T00:15:00.0Z"));
|
||||
assertThat(report.getWatermark()).isEqualTo(DateTime.parse("2010-10-17T00:00:00Z"));
|
||||
@@ -167,9 +160,8 @@ public class RdeReportActionTest {
|
||||
|
||||
@TestOfyAndSql
|
||||
void testRunWithLock_withPrefix() throws Exception {
|
||||
when(httpResponse.getResponseCode()).thenReturn(SC_OK);
|
||||
when(httpResponse.getContent()).thenReturn(IIRDEA_GOOD_XML.read());
|
||||
when(urlFetchService.fetch(request.capture())).thenReturn(httpResponse);
|
||||
when(httpUrlConnection.getResponseCode()).thenReturn(SC_OK);
|
||||
when(httpUrlConnection.getInputStream()).thenReturn(IIRDEA_GOOD_XML.openStream());
|
||||
RdeReportAction action = createAction();
|
||||
action.prefix = Optional.of("job-name/");
|
||||
gcsUtils.delete(reportFile);
|
||||
@@ -182,16 +174,14 @@ public class RdeReportActionTest {
|
||||
assertThat(response.getPayload()).isEqualTo("OK test 2006-06-06T00:00:00.000Z\n");
|
||||
|
||||
// Verify the HTTP request was correct.
|
||||
assertThat(request.getValue().getMethod()).isSameInstanceAs(PUT);
|
||||
assertThat(request.getValue().getURL().getProtocol()).isEqualTo("https");
|
||||
assertThat(request.getValue().getURL().getPath()).endsWith("/test/20101017001");
|
||||
Map<String, String> headers = mapifyHeaders(request.getValue().getHeaders());
|
||||
assertThat(headers).containsEntry("CONTENT_TYPE", "text/xml");
|
||||
assertThat(headers)
|
||||
.containsEntry("AUTHORIZATION", "Basic dGVzdF9yeTpmb28=");
|
||||
verify(httpUrlConnection).setRequestMethod("PUT");
|
||||
assertThat(httpUrlConnection.getURL().getProtocol()).isEqualTo("https");
|
||||
assertThat(httpUrlConnection.getURL().getPath()).endsWith("/test/20101017001");
|
||||
verify(httpUrlConnection).setRequestProperty("Content-Type", "text/xml; charset=utf-8");
|
||||
verify(httpUrlConnection).setRequestProperty("Authorization", "Basic dGVzdF9yeTpmb28=");
|
||||
|
||||
// Verify the payload XML was the same as what's in testdata/report.xml.
|
||||
XjcRdeReportReport report = parseReport(request.getValue().getPayload());
|
||||
XjcRdeReportReport report = parseReport(connectionOutputStream.toByteArray());
|
||||
assertThat(report.getId()).isEqualTo("20101017001");
|
||||
assertThat(report.getCrDate()).isEqualTo(DateTime.parse("2010-10-17T00:15:00.0Z"));
|
||||
assertThat(report.getWatermark()).isEqualTo(DateTime.parse("2010-10-17T00:00:00Z"));
|
||||
@@ -204,9 +194,8 @@ public class RdeReportActionTest {
|
||||
PGPPublicKey encryptKey = new FakeKeyringModule().get().getRdeStagingEncryptionKey();
|
||||
gcsUtils.createFromBytes(newReport, Ghostryde.encode(REPORT_XML.read(), encryptKey));
|
||||
tm().transact(() -> RdeRevision.saveRevision("test", DateTime.parse("2006-06-06TZ"), FULL, 1));
|
||||
when(httpResponse.getResponseCode()).thenReturn(SC_OK);
|
||||
when(httpResponse.getContent()).thenReturn(IIRDEA_GOOD_XML.read());
|
||||
when(urlFetchService.fetch(request.capture())).thenReturn(httpResponse);
|
||||
when(httpUrlConnection.getResponseCode()).thenReturn(SC_OK);
|
||||
when(httpUrlConnection.getInputStream()).thenReturn(IIRDEA_GOOD_XML.openStream());
|
||||
createAction().runWithLock(loadRdeReportCursor());
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
@@ -239,9 +228,8 @@ public class RdeReportActionTest {
|
||||
|
||||
@TestOfyAndSql
|
||||
void testRunWithLock_badRequest_throws500WithErrorInfo() throws Exception {
|
||||
when(httpResponse.getResponseCode()).thenReturn(SC_BAD_REQUEST);
|
||||
when(httpResponse.getContent()).thenReturn(IIRDEA_BAD_XML.read());
|
||||
when(urlFetchService.fetch(request.capture())).thenReturn(httpResponse);
|
||||
when(httpUrlConnection.getResponseCode()).thenReturn(SC_BAD_REQUEST);
|
||||
when(httpUrlConnection.getInputStream()).thenReturn(IIRDEA_BAD_XML.openStream());
|
||||
InternalServerErrorException thrown =
|
||||
assertThrows(
|
||||
InternalServerErrorException.class,
|
||||
@@ -252,18 +240,17 @@ public class RdeReportActionTest {
|
||||
@TestOfyAndSql
|
||||
void testRunWithLock_fetchFailed_throwsRuntimeException() throws Exception {
|
||||
class ExpectedThrownException extends RuntimeException {}
|
||||
when(urlFetchService.fetch(any(HTTPRequest.class))).thenThrow(new ExpectedThrownException());
|
||||
when(httpUrlConnection.getResponseCode()).thenThrow(new ExpectedThrownException());
|
||||
assertThrows(
|
||||
ExpectedThrownException.class, () -> createAction().runWithLock(loadRdeReportCursor()));
|
||||
}
|
||||
|
||||
@TestOfyAndSql
|
||||
void testRunWithLock_socketTimeout_doesRetry() throws Exception {
|
||||
when(httpResponse.getResponseCode()).thenReturn(SC_OK);
|
||||
when(httpResponse.getContent()).thenReturn(IIRDEA_GOOD_XML.read());
|
||||
when(urlFetchService.fetch(request.capture()))
|
||||
when(httpUrlConnection.getInputStream()).thenReturn(IIRDEA_GOOD_XML.openStream());
|
||||
when(httpUrlConnection.getResponseCode())
|
||||
.thenThrow(new SocketTimeoutException())
|
||||
.thenReturn(httpResponse);
|
||||
.thenReturn(SC_OK);
|
||||
createAction().runWithLock(loadRdeReportCursor());
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
|
||||
@@ -274,14 +261,6 @@ public class RdeReportActionTest {
|
||||
return loadByKey(Cursor.createVKey(RDE_REPORT, "test")).getCursorTime();
|
||||
}
|
||||
|
||||
private static ImmutableMap<String, String> mapifyHeaders(Iterable<HTTPHeader> headers) {
|
||||
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
|
||||
for (HTTPHeader header : headers) {
|
||||
builder.put(Ascii.toUpperCase(header.getName().replace('-', '_')), header.getValue());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static XjcRdeReportReport parseReport(byte[] data) {
|
||||
try {
|
||||
return XjcXmlTransformer.unmarshal(XjcRdeReportReport.class, new ByteArrayInputStream(data));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,13 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.util;
|
||||
package google.registry.request;
|
||||
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
|
||||
import static com.google.common.net.MediaType.CSV_UTF_8;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.util.UrlFetchUtils.setPayloadMultipart;
|
||||
import static google.registry.request.UrlConnectionUtils.setPayloadMultipart;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -27,22 +27,19 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPHeader;
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
/** Unit tests for {@link UrlFetchUtils}. */
|
||||
class UrlFetchUtilsTest {
|
||||
|
||||
@RegisterExtension final AppEngineExtension appEngine = AppEngineExtension.builder().build();
|
||||
/** Tests for {@link UrlConnectionUtils}. */
|
||||
public class UrlConnectionUtilsTest {
|
||||
|
||||
private final Random random = mock(Random.class);
|
||||
|
||||
@@ -58,25 +55,28 @@ class UrlFetchUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetPayloadMultipart() {
|
||||
HTTPRequest request = mock(HTTPRequest.class);
|
||||
void testSetPayloadMultipart() throws Exception {
|
||||
HttpsURLConnection connection = mock(HttpsURLConnection.class);
|
||||
ByteArrayOutputStream connectionOutputStream = new ByteArrayOutputStream();
|
||||
when(connection.getOutputStream()).thenReturn(connectionOutputStream);
|
||||
setPayloadMultipart(
|
||||
request,
|
||||
connection,
|
||||
"lol",
|
||||
"cat",
|
||||
CSV_UTF_8,
|
||||
"The nice people at the store say hello. ヘ(◕。◕ヘ)",
|
||||
random);
|
||||
ArgumentCaptor<HTTPHeader> headerCaptor = ArgumentCaptor.forClass(HTTPHeader.class);
|
||||
verify(request, times(2)).addHeader(headerCaptor.capture());
|
||||
List<HTTPHeader> addedHeaders = headerCaptor.getAllValues();
|
||||
assertThat(addedHeaders.get(0).getName()).isEqualTo(CONTENT_TYPE);
|
||||
assertThat(addedHeaders.get(0).getValue())
|
||||
.isEqualTo(
|
||||
"multipart/form-data; "
|
||||
+ "boundary=\"------------------------------AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"");
|
||||
assertThat(addedHeaders.get(1).getName()).isEqualTo(CONTENT_LENGTH);
|
||||
assertThat(addedHeaders.get(1).getValue()).isEqualTo("294");
|
||||
ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(connection, times(2)).setRequestProperty(keyCaptor.capture(), valueCaptor.capture());
|
||||
List<String> addedKeys = keyCaptor.getAllValues();
|
||||
assertThat(addedKeys).containsExactly(CONTENT_TYPE, CONTENT_LENGTH);
|
||||
List<String> addedValues = valueCaptor.getAllValues();
|
||||
assertThat(addedValues)
|
||||
.containsExactly(
|
||||
"multipart/form-data;"
|
||||
+ " boundary=\"------------------------------AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"",
|
||||
"294");
|
||||
String payload =
|
||||
"--------------------------------AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n"
|
||||
+ "Content-Disposition: form-data; name=\"lol\"; filename=\"cat\"\r\n"
|
||||
@@ -84,18 +84,20 @@ class UrlFetchUtilsTest {
|
||||
+ "\r\n"
|
||||
+ "The nice people at the store say hello. ヘ(◕。◕ヘ)\r\n"
|
||||
+ "--------------------------------AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA--\r\n";
|
||||
verify(request).setPayload(payload.getBytes(UTF_8));
|
||||
verifyNoMoreInteractions(request);
|
||||
verify(connection).setDoOutput(true);
|
||||
verify(connection).getOutputStream();
|
||||
assertThat(connectionOutputStream.toByteArray()).isEqualTo(payload.getBytes(UTF_8));
|
||||
verifyNoMoreInteractions(connection);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetPayloadMultipart_boundaryInPayload() {
|
||||
HTTPRequest request = mock(HTTPRequest.class);
|
||||
HttpsURLConnection connection = mock(HttpsURLConnection.class);
|
||||
String payload = "I screamed------------------------------AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHHH";
|
||||
IllegalStateException thrown =
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> setPayloadMultipart(request, "lol", "cat", CSV_UTF_8, payload, random));
|
||||
() -> setPayloadMultipart(connection, "lol", "cat", CSV_UTF_8, payload, random));
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
@@ -1,46 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.testing;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A fake {@link URLFetchService} that serves constructed {@link HTTPResponse} objects from
|
||||
* a simple {@link Map} ({@link URL} to {@link HTTPResponse}) lookup.
|
||||
*/
|
||||
public class FakeURLFetchService extends ForwardingURLFetchService {
|
||||
|
||||
private Map<URL, HTTPResponse> backingMap;
|
||||
|
||||
public FakeURLFetchService(Map<URL, HTTPResponse> backingMap) {
|
||||
this.backingMap = backingMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HTTPResponse fetch(HTTPRequest request) {
|
||||
URL requestURL = request.getURL();
|
||||
if (backingMap.containsKey(requestURL)) {
|
||||
return backingMap.get(requestURL);
|
||||
} else {
|
||||
return new HTTPResponse(HttpURLConnection.HTTP_NOT_FOUND, null, null, ImmutableList.of());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.testing;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import google.registry.request.UrlConnectionService;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** A fake {@link UrlConnectionService} with a mocked HTTP connection for testing. */
|
||||
public class FakeUrlConnectionService implements UrlConnectionService {
|
||||
|
||||
private final HttpURLConnection mockConnection;
|
||||
private final List<URL> connectedUrls;
|
||||
|
||||
public FakeUrlConnectionService(HttpURLConnection mockConnection) {
|
||||
this(mockConnection, new ArrayList<>());
|
||||
}
|
||||
|
||||
public FakeUrlConnectionService(HttpURLConnection mockConnection, List<URL> connectedUrls) {
|
||||
this.mockConnection = mockConnection;
|
||||
this.connectedUrls = connectedUrls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpURLConnection createConnection(URL url) {
|
||||
connectedUrls.add(url);
|
||||
when(mockConnection.getURL()).thenReturn(url);
|
||||
return mockConnection;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.testing;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* An implementation of the {@link URLFetchService} interface that forwards all requests through
|
||||
* a synchronous fetch call.
|
||||
*/
|
||||
public abstract class ForwardingURLFetchService implements URLFetchService {
|
||||
|
||||
@Override
|
||||
public HTTPResponse fetch(URL url) throws IOException {
|
||||
return fetch(new HTTPRequest(url)); // Defaults to HTTPMethod.GET
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<HTTPResponse> fetchAsync(URL url) {
|
||||
return fetchAsync(new HTTPRequest(url)); // Defaults to HTTPMethod.GET
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<HTTPResponse> fetchAsync(HTTPRequest request) {
|
||||
try {
|
||||
return Futures.immediateFuture(fetch(request));
|
||||
} catch (Exception e) {
|
||||
return Futures.immediateFailedFuture(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,21 +19,22 @@ import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
|
||||
import static com.google.common.net.HttpHeaders.LOCATION;
|
||||
import static com.google.common.net.MediaType.FORM_DATA;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.newDomainBase;
|
||||
import static google.registry.testing.DatabaseHelper.persistDomainAndEnqueueLordn;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
|
||||
import static google.registry.util.UrlFetchUtils.getHeaderFirst;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.startsWith;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -43,10 +44,6 @@ import com.google.appengine.api.taskqueue.TaskHandle;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions.Method;
|
||||
import com.google.appengine.api.taskqueue.TransientFailureException;
|
||||
import com.google.appengine.api.urlfetch.HTTPHeader;
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.apphosting.api.DeadlineExceededException;
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -57,11 +54,15 @@ import google.registry.model.tld.Registry;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeSleeper;
|
||||
import google.registry.testing.FakeUrlConnectionService;
|
||||
import google.registry.testing.InjectExtension;
|
||||
import google.registry.testing.TaskQueueHelper.TaskMatcher;
|
||||
import google.registry.util.Retrier;
|
||||
import google.registry.util.TaskQueueUtils;
|
||||
import google.registry.util.UrlFetchException;
|
||||
import google.registry.util.UrlConnectionException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
@@ -69,17 +70,9 @@ import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
/** Unit tests for {@link NordnUploadAction}. */
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class NordnUploadActionTest {
|
||||
|
||||
private static final String CLAIMS_CSV =
|
||||
@@ -101,29 +94,30 @@ class NordnUploadActionTest {
|
||||
AppEngineExtension.builder().withDatastoreAndCloudSql().withTaskQueue().build();
|
||||
|
||||
@RegisterExtension public final InjectExtension inject = new InjectExtension();
|
||||
|
||||
@Mock private URLFetchService fetchService;
|
||||
@Mock private HTTPResponse httpResponse;
|
||||
@Captor private ArgumentCaptor<HTTPRequest> httpRequestCaptor;
|
||||
|
||||
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2010-05-01T10:11:12Z"));
|
||||
private final LordnRequestInitializer lordnRequestInitializer =
|
||||
new LordnRequestInitializer(Optional.of("attack"));
|
||||
private final NordnUploadAction action = new NordnUploadAction();
|
||||
|
||||
private final HttpURLConnection httpUrlConnection = mock(HttpURLConnection.class);
|
||||
private final ByteArrayOutputStream connectionOutputStream = new ByteArrayOutputStream();
|
||||
private final FakeUrlConnectionService urlConnectionService =
|
||||
new FakeUrlConnectionService(httpUrlConnection);
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
inject.setStaticField(Ofy.class, "clock", clock);
|
||||
when(fetchService.fetch(any(HTTPRequest.class))).thenReturn(httpResponse);
|
||||
when(httpResponse.getContent()).thenReturn("Success".getBytes(US_ASCII));
|
||||
when(httpResponse.getResponseCode()).thenReturn(SC_ACCEPTED);
|
||||
when(httpResponse.getHeadersUncombined())
|
||||
.thenReturn(ImmutableList.of(new HTTPHeader(LOCATION, "http://trololol")));
|
||||
when(httpUrlConnection.getInputStream())
|
||||
.thenReturn(new ByteArrayInputStream("Success".getBytes(UTF_8)));
|
||||
when(httpUrlConnection.getResponseCode()).thenReturn(SC_ACCEPTED);
|
||||
when(httpUrlConnection.getHeaderField(LOCATION)).thenReturn("http://trololol");
|
||||
when(httpUrlConnection.getOutputStream()).thenReturn(connectionOutputStream);
|
||||
persistResource(loadRegistrar("TheRegistrar").asBuilder().setIanaIdentifier(99999L).build());
|
||||
createTld("tld");
|
||||
persistResource(Registry.get("tld").asBuilder().setLordnUsername("lolcat").build());
|
||||
action.clock = clock;
|
||||
action.fetchService = fetchService;
|
||||
action.urlConnectionService = urlConnectionService;
|
||||
action.lordnRequestInitializer = lordnRequestInitializer;
|
||||
action.phase = "claims";
|
||||
action.taskQueueUtils = new TaskQueueUtils(new Retrier(new FakeSleeper(clock), 3));
|
||||
@@ -133,7 +127,6 @@ class NordnUploadActionTest {
|
||||
action.retrier = new Retrier(new FakeSleeper(clock), 3);
|
||||
}
|
||||
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@Test
|
||||
void test_convertTasksToCsv() {
|
||||
List<TaskHandle> tasks =
|
||||
@@ -145,7 +138,6 @@ class NordnUploadActionTest {
|
||||
.isEqualTo("1,2010-05-01T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
|
||||
}
|
||||
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@Test
|
||||
void test_convertTasksToCsv_dedupesDuplicates() {
|
||||
List<TaskHandle> tasks =
|
||||
@@ -158,14 +150,12 @@ class NordnUploadActionTest {
|
||||
.isEqualTo("1,2010-05-01T10:11:12.000Z,3\ncol1,col2\ncsvLine1\ncsvLine2\nending\n");
|
||||
}
|
||||
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@Test
|
||||
void test_convertTasksToCsv_doesntFailOnEmptyTasks() {
|
||||
assertThat(NordnUploadAction.convertTasksToCsv(ImmutableList.of(), clock.nowUtc(), "col1,col2"))
|
||||
.isEqualTo("1,2010-05-01T10:11:12.000Z,0\ncol1,col2\n");
|
||||
}
|
||||
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@Test
|
||||
void test_convertTasksToCsv_throwsNpeOnNullTasks() {
|
||||
assertThrows(
|
||||
@@ -173,7 +163,6 @@ class NordnUploadActionTest {
|
||||
() -> NordnUploadAction.convertTasksToCsv(null, clock.nowUtc(), "header"));
|
||||
}
|
||||
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
void test_loadAllTasks_retryLogic_thirdTrysTheCharm() {
|
||||
@@ -186,7 +175,6 @@ class NordnUploadActionTest {
|
||||
assertThat(action.loadAllTasks(queue, "tld")).containsExactly(task);
|
||||
}
|
||||
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@Test
|
||||
void test_loadAllTasks_retryLogic_allFailures() {
|
||||
Queue queue = mock(Queue.class);
|
||||
@@ -201,47 +189,47 @@ class NordnUploadActionTest {
|
||||
void testRun_claimsMode_appendsTldAndClaimsToRequestUrl() throws Exception {
|
||||
persistClaimsModeDomain();
|
||||
action.run();
|
||||
assertThat(getCapturedHttpRequest().getURL())
|
||||
.isEqualTo(new URL("http://127.0.0.1/LORDN/tld/claims"));
|
||||
assertThat(httpUrlConnection.getURL()).isEqualTo(new URL("http://127.0.0.1/LORDN/tld/claims"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_sunriseMode_appendsTldAndClaimsToRequestUrl() throws Exception {
|
||||
persistSunriseModeDomain();
|
||||
action.run();
|
||||
assertThat(getCapturedHttpRequest().getURL())
|
||||
.isEqualTo(new URL("http://127.0.0.1/LORDN/tld/sunrise"));
|
||||
assertThat(httpUrlConnection.getURL()).isEqualTo(new URL("http://127.0.0.1/LORDN/tld/sunrise"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_usesMultipartContentType() throws Exception {
|
||||
persistClaimsModeDomain();
|
||||
action.run();
|
||||
assertThat(getHeaderFirst(getCapturedHttpRequest(), CONTENT_TYPE).get())
|
||||
.startsWith("multipart/form-data; boundary=");
|
||||
verify(httpUrlConnection)
|
||||
.setRequestProperty(eq(CONTENT_TYPE), startsWith("multipart/form-data; boundary="));
|
||||
verify(httpUrlConnection).setRequestMethod("POST");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_hasPassword_setsAuthorizationHeader() throws Exception {
|
||||
void testRun_hasPassword_setsAuthorizationHeader() {
|
||||
persistClaimsModeDomain();
|
||||
action.run();
|
||||
assertThat(getHeaderFirst(getCapturedHttpRequest(), AUTHORIZATION))
|
||||
.hasValue("Basic bG9sY2F0OmF0dGFjaw=="); // echo -n lolcat:attack | base64
|
||||
verify(httpUrlConnection)
|
||||
.setRequestProperty(
|
||||
AUTHORIZATION, "Basic bG9sY2F0OmF0dGFjaw=="); // echo -n lolcat:attack | base64
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_noPassword_doesntSendAuthorizationHeader() throws Exception {
|
||||
void testRun_noPassword_doesntSendAuthorizationHeader() {
|
||||
action.lordnRequestInitializer = new LordnRequestInitializer(Optional.empty());
|
||||
persistClaimsModeDomain();
|
||||
action.run();
|
||||
assertThat(getHeaderFirst(getCapturedHttpRequest(), AUTHORIZATION)).isEmpty();
|
||||
verify(httpUrlConnection, times(0)).setRequestProperty(eq(AUTHORIZATION), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_claimsMode_payloadMatchesClaimsCsv() throws Exception {
|
||||
void testRun_claimsMode_payloadMatchesClaimsCsv() {
|
||||
persistClaimsModeDomain();
|
||||
action.run();
|
||||
assertThat(new String(getCapturedHttpRequest().getPayload(), UTF_8)).contains(CLAIMS_CSV);
|
||||
assertThat(new String(connectionOutputStream.toByteArray(), UTF_8)).contains(CLAIMS_CSV);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -257,19 +245,19 @@ class NordnUploadActionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRun_sunriseMode_payloadMatchesSunriseCsv() throws Exception {
|
||||
void testRun_sunriseMode_payloadMatchesSunriseCsv() {
|
||||
persistSunriseModeDomain();
|
||||
action.run();
|
||||
assertThat(new String(getCapturedHttpRequest().getPayload(), UTF_8)).contains(SUNRISE_CSV);
|
||||
assertThat(new String(connectionOutputStream.toByteArray(), UTF_8)).contains(SUNRISE_CSV);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_noResponseContent_stillWorksNormally() throws Exception {
|
||||
// Returning null only affects logging.
|
||||
when(httpResponse.getContent()).thenReturn(null);
|
||||
when(httpUrlConnection.getInputStream()).thenReturn(new ByteArrayInputStream(new byte[] {}));
|
||||
persistSunriseModeDomain();
|
||||
action.run();
|
||||
assertThat(new String(getCapturedHttpRequest().getPayload(), UTF_8)).contains(SUNRISE_CSV);
|
||||
assertThat(new String(connectionOutputStream.toByteArray(), UTF_8)).contains(SUNRISE_CSV);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -284,7 +272,6 @@ class NordnUploadActionTest {
|
||||
.header(CONTENT_TYPE, FORM_DATA.toString()));
|
||||
}
|
||||
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@Test
|
||||
void testFailure_nullRegistryUser() {
|
||||
persistClaimsModeDomain();
|
||||
@@ -293,17 +280,11 @@ class NordnUploadActionTest {
|
||||
assertThat(thrown).hasMessageThat().contains("lordnUsername is not set for tld.");
|
||||
}
|
||||
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@Test
|
||||
void testFetchFailure() {
|
||||
void testFetchFailure() throws Exception {
|
||||
persistClaimsModeDomain();
|
||||
when(httpResponse.getResponseCode()).thenReturn(SC_INTERNAL_SERVER_ERROR);
|
||||
assertThrows(UrlFetchException.class, action::run);
|
||||
}
|
||||
|
||||
private HTTPRequest getCapturedHttpRequest() throws Exception {
|
||||
verify(fetchService).fetch(httpRequestCaptor.capture());
|
||||
return httpRequestCaptor.getAllValues().get(0);
|
||||
when(httpUrlConnection.getResponseCode()).thenReturn(SC_INTERNAL_SERVER_ERROR);
|
||||
assertThrows(UrlConnectionException.class, action::run);
|
||||
}
|
||||
|
||||
private void persistClaimsModeDomain() {
|
||||
|
||||
@@ -16,39 +16,34 @@ package google.registry.tmch;
|
||||
|
||||
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.UrlFetchUtils.getHeaderFirst;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.request.HttpException.ConflictException;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.FakeUrlConnectionService;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/** Unit tests for {@link NordnVerifyAction}. */
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class NordnVerifyActionTest {
|
||||
|
||||
private static final String LOG_ACCEPTED =
|
||||
@@ -81,84 +76,83 @@ class NordnVerifyActionTest {
|
||||
public final AppEngineExtension appEngine =
|
||||
AppEngineExtension.builder().withDatastoreAndCloudSql().withTaskQueue().build();
|
||||
|
||||
@Mock private URLFetchService fetchService;
|
||||
@Mock private HTTPResponse httpResponse;
|
||||
@Captor private ArgumentCaptor<HTTPRequest> httpRequestCaptor;
|
||||
|
||||
private final FakeResponse response = new FakeResponse();
|
||||
private final LordnRequestInitializer lordnRequestInitializer =
|
||||
new LordnRequestInitializer(Optional.of("attack"));
|
||||
private final NordnVerifyAction action = new NordnVerifyAction();
|
||||
|
||||
private final HttpURLConnection httpUrlConnection = mock(HttpURLConnection.class);
|
||||
private final FakeUrlConnectionService urlConnectionService =
|
||||
new FakeUrlConnectionService(httpUrlConnection);
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
when(httpResponse.getResponseCode()).thenReturn(SC_OK);
|
||||
when(httpResponse.getContent()).thenReturn(LOG_ACCEPTED.getBytes(UTF_8));
|
||||
when(fetchService.fetch(any(HTTPRequest.class))).thenReturn(httpResponse);
|
||||
createTld("gtld");
|
||||
persistResource(Registry.get("gtld").asBuilder().setLordnUsername("lolcat").build());
|
||||
action.tld = "gtld";
|
||||
action.fetchService = fetchService;
|
||||
action.urlConnectionService = urlConnectionService;
|
||||
when(httpUrlConnection.getResponseCode()).thenReturn(SC_OK);
|
||||
when(httpUrlConnection.getInputStream())
|
||||
.thenReturn(new ByteArrayInputStream(LOG_ACCEPTED.getBytes(UTF_8)));
|
||||
action.lordnRequestInitializer = lordnRequestInitializer;
|
||||
action.response = response;
|
||||
action.url = new URL("http://127.0.0.1/blobio");
|
||||
}
|
||||
|
||||
private HTTPRequest getCapturedHttpRequest() throws Exception {
|
||||
verify(fetchService).fetch(httpRequestCaptor.capture());
|
||||
return httpRequestCaptor.getAllValues().get(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_sendHttpRequest_urlIsCorrect() throws Exception {
|
||||
action.run();
|
||||
assertThat(getCapturedHttpRequest().getURL()).isEqualTo(new URL("http://127.0.0.1/blobio"));
|
||||
assertThat(httpUrlConnection.getURL()).isEqualTo(new URL("http://127.0.0.1/blobio"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_hasLordnPassword_sendsAuthorizationHeader() throws Exception {
|
||||
void testSuccess_hasLordnPassword_sendsAuthorizationHeader() {
|
||||
action.run();
|
||||
assertThat(getHeaderFirst(getCapturedHttpRequest(), AUTHORIZATION))
|
||||
.hasValue("Basic bG9sY2F0OmF0dGFjaw=="); // echo -n lolcat:attack | base64
|
||||
verify(httpUrlConnection)
|
||||
.setRequestProperty(
|
||||
AUTHORIZATION, "Basic bG9sY2F0OmF0dGFjaw=="); // echo -n lolcat:attack | base64
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_noLordnPassword_doesntSetAuthorizationHeader() throws Exception {
|
||||
void testSuccess_noLordnPassword_doesntSetAuthorizationHeader() {
|
||||
action.lordnRequestInitializer = new LordnRequestInitializer(Optional.empty());
|
||||
action.run();
|
||||
assertThat(getHeaderFirst(getCapturedHttpRequest(), AUTHORIZATION)).isEmpty();
|
||||
verify(httpUrlConnection, times(0)).setRequestProperty(eq(AUTHORIZATION), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void successVerifyRejected() throws Exception {
|
||||
when(httpResponse.getContent()).thenReturn(LOG_REJECTED.getBytes(UTF_8));
|
||||
when(httpUrlConnection.getInputStream())
|
||||
.thenReturn(new ByteArrayInputStream(LOG_REJECTED.getBytes(UTF_8)));
|
||||
LordnLog lastLog = action.verify();
|
||||
assertThat(lastLog.getStatus()).isEqualTo(LordnLog.Status.REJECTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void successVerifyWarnings() throws Exception {
|
||||
when(httpResponse.getContent()).thenReturn(LOG_WARNINGS.getBytes(UTF_8));
|
||||
when(httpUrlConnection.getInputStream())
|
||||
.thenReturn(new ByteArrayInputStream(LOG_WARNINGS.getBytes(UTF_8)));
|
||||
LordnLog lastLog = action.verify();
|
||||
assertThat(lastLog.hasWarnings()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void successVerifyErrors() throws Exception {
|
||||
when(httpResponse.getContent()).thenReturn(LOG_ERRORS.getBytes(UTF_8));
|
||||
when(httpUrlConnection.getInputStream())
|
||||
.thenReturn(new ByteArrayInputStream(LOG_ERRORS.getBytes(UTF_8)));
|
||||
LordnLog lastLog = action.verify();
|
||||
assertThat(lastLog.hasWarnings()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void failureVerifyUnauthorized() {
|
||||
when(httpResponse.getResponseCode()).thenReturn(SC_UNAUTHORIZED);
|
||||
void failureVerifyUnauthorized() throws Exception {
|
||||
when(httpUrlConnection.getResponseCode()).thenReturn(SC_UNAUTHORIZED);
|
||||
assertThrows(Exception.class, action::run);
|
||||
}
|
||||
|
||||
@Test
|
||||
void failureVerifyNotReady() {
|
||||
when(httpResponse.getResponseCode()).thenReturn(SC_NO_CONTENT);
|
||||
void failureVerifyNotReady() throws Exception {
|
||||
when(httpUrlConnection.getResponseCode()).thenReturn(SC_NO_CONTENT);
|
||||
ConflictException thrown = assertThrows(ConflictException.class, action::run);
|
||||
assertThat(thrown).hasMessageThat().contains("Not ready");
|
||||
}
|
||||
|
||||
@@ -15,21 +15,19 @@
|
||||
package google.registry.tmch;
|
||||
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.BouncyCastleProviderExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeUrlConnectionService;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/** Common code for unit tests of classes that extend {@link Marksdb}. */
|
||||
@@ -46,19 +44,19 @@ abstract class TmchActionTestCase {
|
||||
@RegisterExtension
|
||||
public final BouncyCastleProviderExtension bouncy = new BouncyCastleProviderExtension();
|
||||
|
||||
@Mock URLFetchService fetchService;
|
||||
@Mock HTTPResponse httpResponse;
|
||||
@Captor ArgumentCaptor<HTTPRequest> httpRequest;
|
||||
|
||||
final FakeClock clock = new FakeClock();
|
||||
final Marksdb marksdb = new Marksdb();
|
||||
|
||||
protected final HttpURLConnection httpUrlConnection = mock(HttpURLConnection.class);
|
||||
protected final ArrayList<URL> connectedUrls = new ArrayList<>();
|
||||
protected FakeUrlConnectionService urlConnectionService =
|
||||
new FakeUrlConnectionService(httpUrlConnection, connectedUrls);
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEachTmchActionTestCase() throws Exception {
|
||||
marksdb.fetchService = fetchService;
|
||||
marksdb.tmchMarksdbUrl = MARKSDB_URL;
|
||||
marksdb.marksdbPublicKey = TmchData.loadPublicKey(TmchTestData.loadBytes("pubkey"));
|
||||
when(fetchService.fetch(any(HTTPRequest.class))).thenReturn(httpResponse);
|
||||
when(httpResponse.getResponseCode()).thenReturn(SC_OK);
|
||||
marksdb.urlConnectionService = urlConnectionService;
|
||||
when(httpUrlConnection.getResponseCode()).thenReturn(SC_OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import google.registry.config.RegistryConfig.ConfigModule.TmchCaMode;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.SignatureException;
|
||||
@@ -44,19 +45,22 @@ class TmchCrlActionTest extends TmchActionTestCase {
|
||||
@Test
|
||||
void testSuccess() throws Exception {
|
||||
clock.setTo(DateTime.parse("2013-07-24TZ"));
|
||||
when(httpResponse.getContent()).thenReturn(
|
||||
readResourceBytes(TmchCertificateAuthority.class, "icann-tmch.crl").read());
|
||||
when(httpUrlConnection.getInputStream())
|
||||
.thenReturn(
|
||||
new ByteArrayInputStream(
|
||||
readResourceBytes(TmchCertificateAuthority.class, "icann-tmch.crl").read()));
|
||||
newTmchCrlAction(TmchCaMode.PRODUCTION).run();
|
||||
verify(httpResponse).getContent();
|
||||
verify(fetchService).fetch(httpRequest.capture());
|
||||
assertThat(httpRequest.getValue().getURL().toString()).isEqualTo("http://sloth.lol/tmch.crl");
|
||||
verify(httpUrlConnection).getInputStream();
|
||||
assertThat(connectedUrls).containsExactly(new URL("http://sloth.lol/tmch.crl"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_crlTooOld() throws Exception {
|
||||
clock.setTo(DateTime.parse("2020-01-01TZ"));
|
||||
when(httpResponse.getContent())
|
||||
.thenReturn(loadBytes(TmchCrlActionTest.class, "icann-tmch-pilot-old.crl").read());
|
||||
when(httpUrlConnection.getInputStream())
|
||||
.thenReturn(
|
||||
new ByteArrayInputStream(
|
||||
loadBytes(TmchCrlActionTest.class, "icann-tmch-pilot-old.crl").read()));
|
||||
TmchCrlAction action = newTmchCrlAction(TmchCaMode.PILOT);
|
||||
Exception e = assertThrows(Exception.class, action::run);
|
||||
assertThat(e).hasCauseThat().isInstanceOf(CRLException.class);
|
||||
@@ -69,8 +73,10 @@ class TmchCrlActionTest extends TmchActionTestCase {
|
||||
@Test
|
||||
void testFailure_crlNotSignedByRoot() throws Exception {
|
||||
clock.setTo(DateTime.parse("2013-07-24TZ"));
|
||||
when(httpResponse.getContent())
|
||||
.thenReturn(readResourceBytes(TmchCertificateAuthority.class, "icann-tmch.crl").read());
|
||||
when(httpUrlConnection.getInputStream())
|
||||
.thenReturn(
|
||||
new ByteArrayInputStream(
|
||||
readResourceBytes(TmchCertificateAuthority.class, "icann-tmch.crl").read()));
|
||||
Exception e = assertThrows(Exception.class, newTmchCrlAction(TmchCaMode.PILOT)::run);
|
||||
assertThat(e).hasCauseThat().isInstanceOf(SignatureException.class);
|
||||
assertThat(e).hasCauseThat().hasMessageThat().isEqualTo("Signature does not match.");
|
||||
@@ -79,8 +85,10 @@ class TmchCrlActionTest extends TmchActionTestCase {
|
||||
@Test
|
||||
void testFailure_crlNotYetValid() throws Exception {
|
||||
clock.setTo(DateTime.parse("1984-01-01TZ"));
|
||||
when(httpResponse.getContent()).thenReturn(
|
||||
readResourceBytes(TmchCertificateAuthority.class, "icann-tmch-pilot.crl").read());
|
||||
when(httpUrlConnection.getInputStream())
|
||||
.thenReturn(
|
||||
new ByteArrayInputStream(
|
||||
readResourceBytes(TmchCertificateAuthority.class, "icann-tmch-pilot.crl").read()));
|
||||
Exception e = assertThrows(Exception.class, newTmchCrlAction(TmchCaMode.PILOT)::run);
|
||||
assertThat(e).hasCauseThat().isInstanceOf(CertificateNotYetValidException.class);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.tmch;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -22,6 +23,8 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import google.registry.model.tmch.ClaimsList;
|
||||
import google.registry.model.tmch.ClaimsListDao;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -39,15 +42,13 @@ class TmchDnlActionTest extends TmchActionTestCase {
|
||||
@Test
|
||||
void testDnl() throws Exception {
|
||||
assertThat(ClaimsListDao.get().getClaimKey("xn----7sbejwbn3axu3d")).isEmpty();
|
||||
when(httpResponse.getContent())
|
||||
.thenReturn(TmchTestData.loadBytes("dnl-latest.csv").read())
|
||||
.thenReturn(TmchTestData.loadBytes("dnl-latest.sig").read());
|
||||
when(httpUrlConnection.getInputStream())
|
||||
.thenReturn(new ByteArrayInputStream(TmchTestData.loadBytes("dnl-latest.csv").read()))
|
||||
.thenReturn(new ByteArrayInputStream(TmchTestData.loadBytes("dnl-latest.sig").read()));
|
||||
newTmchDnlAction().run();
|
||||
verify(fetchService, times(2)).fetch(httpRequest.capture());
|
||||
assertThat(httpRequest.getAllValues().get(0).getURL().toString())
|
||||
.isEqualTo(MARKSDB_URL + "/dnl/dnl-latest.csv");
|
||||
assertThat(httpRequest.getAllValues().get(1).getURL().toString())
|
||||
.isEqualTo(MARKSDB_URL + "/dnl/dnl-latest.sig");
|
||||
verify(httpUrlConnection, times(2)).getInputStream();
|
||||
assertThat(connectedUrls.stream().map(URL::toString).collect(toImmutableList()))
|
||||
.containsExactly(MARKSDB_URL + "/dnl/dnl-latest.csv", MARKSDB_URL + "/dnl/dnl-latest.sig");
|
||||
|
||||
// Make sure the contents of testdata/dnl-latest.csv got inserted into the database.
|
||||
ClaimsList claimsList = ClaimsListDao.get();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.tmch;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.tmch.TmchTestData.loadBytes;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -21,6 +22,8 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import google.registry.model.smd.SignedMarkRevocationList;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -42,15 +45,14 @@ class TmchSmdrlActionTest extends TmchActionTestCase {
|
||||
SignedMarkRevocationList smdrl = SignedMarkRevocationList.get();
|
||||
assertThat(smdrl.isSmdRevoked("0000001681375789102250-65535", now)).isFalse();
|
||||
assertThat(smdrl.isSmdRevoked("0000001681375789102250-65536", now)).isFalse();
|
||||
when(httpResponse.getContent())
|
||||
.thenReturn(loadBytes("smdrl-latest.csv").read())
|
||||
.thenReturn(loadBytes("smdrl-latest.sig").read());
|
||||
when(httpUrlConnection.getInputStream())
|
||||
.thenReturn(new ByteArrayInputStream(loadBytes("smdrl-latest.csv").read()))
|
||||
.thenReturn(new ByteArrayInputStream(loadBytes("smdrl-latest.sig").read()));
|
||||
newTmchSmdrlAction().run();
|
||||
verify(fetchService, times(2)).fetch(httpRequest.capture());
|
||||
assertThat(httpRequest.getAllValues().get(0).getURL().toString())
|
||||
.isEqualTo(MARKSDB_URL + "/smdrl/smdrl-latest.csv");
|
||||
assertThat(httpRequest.getAllValues().get(1).getURL().toString())
|
||||
.isEqualTo(MARKSDB_URL + "/smdrl/smdrl-latest.sig");
|
||||
verify(httpUrlConnection, times(2)).getInputStream();
|
||||
assertThat(connectedUrls.stream().map(URL::toString).collect(toImmutableList()))
|
||||
.containsExactly(
|
||||
MARKSDB_URL + "/smdrl/smdrl-latest.csv", MARKSDB_URL + "/smdrl/smdrl-latest.sig");
|
||||
smdrl = SignedMarkRevocationList.get();
|
||||
assertThat(smdrl.isSmdRevoked("0000001681375789102250-65535", now)).isTrue();
|
||||
assertThat(smdrl.isSmdRevoked("0000001681375789102250-65536", now)).isFalse();
|
||||
|
||||
Reference in New Issue
Block a user