diff --git a/java/google/registry/export/datastore/BUILD b/java/google/registry/export/datastore/BUILD new file mode 100644 index 000000000..25e4e286b --- /dev/null +++ b/java/google/registry/export/datastore/BUILD @@ -0,0 +1,21 @@ +package( + default_visibility = ["//visibility:public"], +) + +licenses(["notice"]) # Apache 2.0 + +java_library( + name = "datastore", + srcs = glob(["*.java"]), + deps = [ + "//java/google/registry/config", + "//third_party/java/google_api_java_client:services_json", + "//third_party/java/google_http_java_client:http", + "//third_party/java/google_http_java_client:json", + "//third_party/java/google_http_java_client:util", + "@com_google_api_client", + "@com_google_dagger", + "@com_google_guava", + "@com_google_http_client_jackson2", + ], +) diff --git a/java/google/registry/export/datastore/DatastoreAdmin.java b/java/google/registry/export/datastore/DatastoreAdmin.java new file mode 100644 index 000000000..88610b02f --- /dev/null +++ b/java/google/registry/export/datastore/DatastoreAdmin.java @@ -0,0 +1,223 @@ +// Copyright 2018 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.export.datastore; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClient; +import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.GenericJson; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.util.Key; +import com.google.common.base.Strings; +import java.util.List; +import java.util.Optional; + +/** + * Java client to Cloud + * Datastore Admin REST API. + */ +public class DatastoreAdmin extends AbstractGoogleJsonClient { + + private static final String ROOT_URL = "https://datastore.googleapis.com/v1/"; + private static final String SERVICE_PATH = ""; + + // GCP project that this instance is associated with. + private final String projectId; + + protected DatastoreAdmin(Builder builder) { + super(builder); + this.projectId = checkNotNull(builder.projectId, "GCP projectId missing."); + } + + /** + * Returns an {@link Export} request that starts exporting all Cloud Datastore databases owned by + * the GCP project identified by {@link #projectId}. + * + *

Typical usage is: + * + *

+   *     {@code Export export = datastoreAdmin.export(parameters ...);}
+   *     {@code Operation operation = export.execute();}
+   *     {@code while (!operation.isSuccessful()) { ...}}
+   * 
+ * + *

Please see the API + * specification of the export method for details. + * + *

The following undocumented behaviors with regard to {@code outputUrlPrefix} have been + * observed: + * + *

+ * + * @param outputUrlPrefix the full resource URL of the external storage location + * @param kinds the datastore 'kinds' to be exported + */ + public Export export(String outputUrlPrefix, List kinds) { + return new Export(new ExportRequest(outputUrlPrefix, kinds)); + } + + /** + * Returns a {@link Get} request that retrieves the details of an export or import {@link + * Operation}. + * + * @param operationName name of the {@code Operation} as returned by an export or import request + */ + public Get get(String operationName) { + return new Get(operationName); + } + + /** + * Returns a {@link ListOperations} request that retrieves all export or import {@link Operation + * operations} matching {@code filter}. + * + *

Sample usage: find all operations started after 2018-10-31 00:00:00 UTC and has stopped: + * + *

+   *     {@code String filter = "metadata.common.startTime>\"2018-10-31T0:0:0Z\" AND done=true";}
+   *     {@code List operations = datastoreAdmin.list(filter);}
+   * 
+ * + *

Please refer to {@link Operation} for how to reference operation properties. + */ + public ListOperations list(String filter) { + checkArgument(!Strings.isNullOrEmpty(filter), "Filter must not be null or empty."); + return new ListOperations(Optional.of(filter)); + } + + /** + * Returns a {@link ListOperations} request that retrieves all export or import {@link Operation * + * operations}. + */ + public ListOperations listAll() { + return new ListOperations(Optional.empty()); + } + + /** Builder for {@link DatastoreAdmin}. */ + public static class Builder extends AbstractGoogleJsonClient.Builder { + + private String projectId; + + public Builder( + HttpTransport httpTransport, + JsonFactory jsonFactory, + HttpRequestInitializer httpRequestInitializer) { + super(httpTransport, jsonFactory, ROOT_URL, SERVICE_PATH, httpRequestInitializer, false); + } + + @Override + public Builder setApplicationName(String applicationName) { + return (Builder) super.setApplicationName(applicationName); + } + + /** Sets the GCP project ID of the Cloud Datastore databases being managed. */ + public Builder setProjectId(String projectId) { + this.projectId = projectId; + return this; + } + + @Override + public DatastoreAdmin build() { + return new DatastoreAdmin(this); + } + } + + /** A request to export Cloud Datastore databases. */ + public class Export extends DatastoreAdminRequest { + + Export(ExportRequest exportRequest) { + super( + DatastoreAdmin.this, + "POST", + "projects/{projectId}:export", + exportRequest, + Operation.class); + set("projectId", projectId); + } + } + + /** A request to retrieve details of an export or import operation. */ + public class Get extends DatastoreAdminRequest { + + Get(String operationName) { + super(DatastoreAdmin.this, "GET", operationName, null, Operation.class); + } + } + + /** A request to retrieve all export or import operations matching a given filter. */ + public class ListOperations extends DatastoreAdminRequest { + + ListOperations(Optional filter) { + super( + DatastoreAdmin.this, + "GET", + "projects/{projectId}/operations", + null, + Operation.OperationList.class); + set("projectId", projectId); + filter.ifPresent(f -> set("filter", f)); + } + } + + /** Base class of all DatastoreAdmin requests. */ + abstract static class DatastoreAdminRequest extends AbstractGoogleJsonClientRequest { + /** + * @param client Google JSON client + * @param requestMethod HTTP Method + * @param uriTemplate URI template for the path relative to the base URL. If it starts with a + * "/" the base path from the base URL will be stripped out. The URI template can also be a + * full URL. URI template expansion is done using {@link + * com.google.api.client.http.UriTemplate#expand(String, String, Object, boolean)} + * @param jsonContent POJO that can be serialized into JSON content or {@code null} for none + * @param responseClass response class to parse into + */ + protected DatastoreAdminRequest( + DatastoreAdmin client, + String requestMethod, + String uriTemplate, + Object jsonContent, + Class responseClass) { + super(client, requestMethod, uriTemplate, jsonContent, responseClass); + } + } + + /** + * Model object that describes the JSON content in an export request. + * + *

Please note that some properties defined in the API are excluded, e.g., {@code databaseId} + * (not supported by Cloud Datastore) and labels (not used by Domain Registry). + */ + @SuppressWarnings("unused") + static class ExportRequest extends GenericJson { + @Key private final String outputUrlPrefix; + @Key private final EntityFilter entityFilter; + + ExportRequest(String outputUrlPrefix, List kinds) { + checkNotNull(outputUrlPrefix, "outputUrlPrefix"); + this.outputUrlPrefix = outputUrlPrefix; + this.entityFilter = new EntityFilter(kinds); + } + } +} diff --git a/java/google/registry/export/datastore/DatastoreAdminModule.java b/java/google/registry/export/datastore/DatastoreAdminModule.java new file mode 100644 index 000000000..322fb0e2f --- /dev/null +++ b/java/google/registry/export/datastore/DatastoreAdminModule.java @@ -0,0 +1,39 @@ +// Copyright 2018 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.export.datastore; + +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import dagger.Module; +import dagger.Provides; +import google.registry.config.CredentialModule; +import google.registry.config.RegistryConfig; +import javax.inject.Singleton; + +/** Dagger module that configures provision of {@link DatastoreAdmin}. */ +@Module +public abstract class DatastoreAdminModule { + + @Singleton + @Provides + static DatastoreAdmin provideDatastoreAdmin( + @CredentialModule.DefaultCredential GoogleCredential credential, + @RegistryConfig.Config("projectId") String projectId) { + return new DatastoreAdmin.Builder( + credential.getTransport(), credential.getJsonFactory(), credential) + .setApplicationName(projectId) + .setProjectId(projectId) + .build(); + } +} diff --git a/java/google/registry/export/datastore/EntityFilter.java b/java/google/registry/export/datastore/EntityFilter.java new file mode 100644 index 000000000..c7f871c50 --- /dev/null +++ b/java/google/registry/export/datastore/EntityFilter.java @@ -0,0 +1,48 @@ +// Copyright 2018 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.export.datastore; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.client.json.GenericJson; +import com.google.api.client.util.Key; +import com.google.common.collect.ImmutableList; +import java.util.List; + +/** + * Model object that describes the Cloud Datastore 'kinds' to be exported or imported. The JSON form + * of this type is found in export/import requests and responses. + * + *

Please note that properties not used by Domain Registry are not included, e.g., {@code + * namespaceIds}. + */ +public class EntityFilter extends GenericJson { + + @Key private List kinds = ImmutableList.of(); + + /** For JSON deserialization. */ + public EntityFilter() {} + + EntityFilter(List kinds) { + checkNotNull(kinds, "kinds"); + checkArgument(!kinds.isEmpty(), "kinds must not be empty"); + this.kinds = ImmutableList.copyOf(kinds); + } + + List getKinds() { + return ImmutableList.copyOf(kinds); + } +} diff --git a/java/google/registry/export/datastore/Operation.java b/java/google/registry/export/datastore/Operation.java new file mode 100644 index 000000000..a1948b162 --- /dev/null +++ b/java/google/registry/export/datastore/Operation.java @@ -0,0 +1,144 @@ +// Copyright 2018 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.export.datastore; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.api.client.json.GenericJson; +import com.google.api.client.util.Key; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import google.registry.export.datastore.DatastoreAdmin.Get; +import java.util.List; + +/** Model object that describes the details of an export or import operation in Cloud Datastore. */ +public class Operation extends GenericJson { + + private static final String STATE_SUCCESS = "SUCCESSFUL"; + private static final String STATE_PROCESSING = "PROCESSING"; + + @Key private String name; + @Key private Metadata metadata; + @Key private boolean done; + + /** For JSON deserialization. */ + public Operation() {} + + /** Returns the name of this operation, which may be used in a {@link Get} request. */ + public String getName() { + checkState(name != null, "Name must not be null."); + return name; + } + + public boolean isDone() { + return done; + } + + public String getState() { + checkState(metadata != null, "Response metadata missing."); + return metadata.getCommonMetadata().getState(); + } + + public boolean isSuccessful() { + checkState(metadata != null, "Response metadata missing."); + return getState().equals(STATE_SUCCESS); + } + + public boolean isProcessing() { + checkState(metadata != null, "Response metadata missing."); + return getState().equals(STATE_PROCESSING); + } + + /** Models the common metadata properties of all operations. */ + public static class CommonMetadata extends GenericJson { + + @Key private String operationType; + @Key private String state; + + public CommonMetadata() {} + + String getOperationType() { + checkState(!Strings.isNullOrEmpty(operationType), "operationType may not be null or empty"); + return operationType; + } + + String getState() { + checkState(!Strings.isNullOrEmpty(state), "state may not be null or empty"); + return state; + } + } + + /** Models the metadata of a Cloud Datatore export or import operation. */ + public static class Metadata extends GenericJson { + @Key("common") + private CommonMetadata commonMetadata; + + @Key private Progress progressEntities; + @Key private Progress progressBytes; + @Key private EntityFilter entityFilter; + @Key private String outputUrlPrefix; + + public Metadata() {} + + CommonMetadata getCommonMetadata() { + checkState(commonMetadata != null, "CommonMetadata field is null."); + return commonMetadata; + } + + public Progress getProgressEntities() { + return progressEntities; + } + + public Progress getProgressBytes() { + return progressBytes; + } + + public EntityFilter getEntityFilter() { + return entityFilter; + } + + public String getOutputUrlPrefix() { + return outputUrlPrefix; + } + } + + /** Progress of an export or import operation. */ + public static class Progress extends GenericJson { + @Key private long workCompleted; + @Key private long workEstimated; + + public Progress() {} + + long getWorkCompleted() { + return workCompleted; + } + + public long getWorkEstimated() { + return workEstimated; + } + } + + /** List of {@link Operation Operations}. */ + public static class OperationList extends GenericJson { + @Key private List operations; + + /** For JSON deserialization. */ + public OperationList() {} + + ImmutableList toList() { + return ImmutableList.copyOf(operations); + } + } +} diff --git a/javatests/google/registry/export/datastore/BUILD b/javatests/google/registry/export/datastore/BUILD new file mode 100644 index 000000000..b07ac7b13 --- /dev/null +++ b/javatests/google/registry/export/datastore/BUILD @@ -0,0 +1,35 @@ +package( + default_testonly = 1, + default_visibility = ["//java/google/registry:registry_project"], +) + +licenses(["notice"]) # Apache 2.0 + +load("//java/com/google/testing/builddefs:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "datastore", + srcs = glob(["*.java"]), + resources = glob(["**/testdata/*.json"]), + deps = [ + "//java/google/registry/export/datastore", + "//javatests/google/registry/testing", + "//third_party/java/google_http_java_client:http", + "//third_party/java/google_http_java_client:javanet", + "//third_party/java/google_http_java_client:util", + "@com_google_api_client", + "@com_google_guava", + "@com_google_http_client", + "@com_google_http_client_jackson2", + "@com_google_truth", + "@com_google_truth_extensions_truth_java8_extension", + "@junit", + "@org_mockito_all", + ], +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["*Test.java"]), + deps = [":datastore"], +) diff --git a/javatests/google/registry/export/datastore/DatastoreAdminTest.java b/javatests/google/registry/export/datastore/DatastoreAdminTest.java new file mode 100644 index 000000000..12aae02c7 --- /dev/null +++ b/javatests/google/registry/export/datastore/DatastoreAdminTest.java @@ -0,0 +1,161 @@ +// Copyright 2018 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.export.datastore; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; + +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.common.collect.ImmutableList; +import google.registry.testing.MockitoJUnitRule; +import google.registry.testing.TestDataHelper; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link DatastoreAdmin}. */ +@RunWith(JUnit4.class) +public class DatastoreAdminTest { + + private static final String AUTH_HEADER_PREFIX = "Bearer "; + private static final String ACCESS_TOKEN = "MyAccessToken"; + private static final ImmutableList KINDS = + ImmutableList.of("Registry", "Registrar", "DomainBase"); + + @Rule public final MockitoJUnitRule mocks = MockitoJUnitRule.create(); + + private HttpTransport httpTransport; + private GoogleCredential googleCredential; + private DatastoreAdmin datastoreAdmin; + + @Before + public void setup() { + httpTransport = new NetHttpTransport(); + googleCredential = + new GoogleCredential.Builder() + .setTransport(httpTransport) + .setJsonFactory(JacksonFactory.getDefaultInstance()) + .setClock(() -> 0) + .build(); + googleCredential.setAccessToken(ACCESS_TOKEN); + googleCredential.setExpiresInSeconds(1_000L); + + datastoreAdmin = + new DatastoreAdmin.Builder( + googleCredential.getTransport(), + googleCredential.getJsonFactory(), + googleCredential) + .setApplicationName("MyApplication") + .setProjectId("MyCloudProject") + .build(); + } + + @Test + public void testExport() throws IOException { + DatastoreAdmin.Export export = datastoreAdmin.export("gs://mybucket/path", KINDS); + HttpRequest httpRequest = export.buildHttpRequest(); + assertThat(httpRequest.getUrl().toString()) + .isEqualTo("https://datastore.googleapis.com/v1/projects/MyCloudProject:export"); + assertThat(httpRequest.getRequestMethod()).isEqualTo("POST"); + + assertThat(getRequestContent(httpRequest)) + .hasValue( + TestDataHelper.loadFile(getClass(), "export_request_content.json") + .replaceAll("[\\s\\n]+", "")); + + simulateSendRequest(httpRequest); + assertThat(getAccessToken(httpRequest)).hasValue(ACCESS_TOKEN); + } + + @Test + public void testGetOperation() throws IOException { + DatastoreAdmin.Get get = + datastoreAdmin.get("projects/MyCloudProject/operations/ASAzNjMwOTEyNjUJ"); + HttpRequest httpRequest = get.buildHttpRequest(); + assertThat(httpRequest.getUrl().toString()) + .isEqualTo( + "https://datastore.googleapis.com/v1/projects/MyCloudProject/operations/ASAzNjMwOTEyNjUJ"); + assertThat(httpRequest.getRequestMethod()).isEqualTo("GET"); + assertThat(httpRequest.getContent()).isNull(); + + simulateSendRequest(httpRequest); + assertThat(getAccessToken(httpRequest)).hasValue(ACCESS_TOKEN); + } + + @Test + public void testListOperations_all() throws IOException { + DatastoreAdmin.ListOperations listOperations = datastoreAdmin.listAll(); + HttpRequest httpRequest = listOperations.buildHttpRequest(); + assertThat(httpRequest.getUrl().toString()) + .isEqualTo("https://datastore.googleapis.com/v1/projects/MyCloudProject/operations"); + assertThat(httpRequest.getRequestMethod()).isEqualTo("GET"); + assertThat(httpRequest.getContent()).isNull(); + + simulateSendRequest(httpRequest); + assertThat(getAccessToken(httpRequest)).hasValue(ACCESS_TOKEN); + } + + @Test + public void testListOperations_filterByStartTime() throws IOException { + DatastoreAdmin.ListOperations listOperations = + datastoreAdmin.list("metadata.common.startTime>\"2018-10-31T00:00:00.0Z\""); + HttpRequest httpRequest = listOperations.buildHttpRequest(); + assertThat(httpRequest.getUrl().toString()) + .isEqualTo( + "https://datastore.googleapis.com/v1/projects/MyCloudProject/operations" + + "?filter=metadata.common.startTime%3E%222018-10-31T00:00:00.0Z%22"); + assertThat(httpRequest.getRequestMethod()).isEqualTo("GET"); + assertThat(httpRequest.getContent()).isNull(); + + simulateSendRequest(httpRequest); + assertThat(getAccessToken(httpRequest)).hasValue(ACCESS_TOKEN); + } + + private static HttpRequest simulateSendRequest(HttpRequest httpRequest) { + try { + httpRequest.setUrl(new GenericUrl("https://localhost:65537")).execute(); + } catch (Exception expected) { + } + return httpRequest; + } + + private static Optional getAccessToken(HttpRequest httpRequest) { + return httpRequest.getHeaders().getAuthorizationAsList().stream() + .filter(header -> header.startsWith(AUTH_HEADER_PREFIX)) + .map(header -> header.substring(AUTH_HEADER_PREFIX.length())) + .findAny(); + } + + private static Optional getRequestContent(HttpRequest httpRequest) throws IOException { + if (httpRequest.getContent() == null) { + return Optional.empty(); + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + httpRequest.getContent().writeTo(outputStream); + outputStream.close(); + return Optional.of(outputStream.toString(StandardCharsets.UTF_8.name())); + } +} diff --git a/javatests/google/registry/export/datastore/EntityFilterTest.java b/javatests/google/registry/export/datastore/EntityFilterTest.java new file mode 100644 index 000000000..ba56ea9c9 --- /dev/null +++ b/javatests/google/registry/export/datastore/EntityFilterTest.java @@ -0,0 +1,80 @@ +// Copyright 2018 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.export.datastore; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.testing.JUnitBackports.assertThrows; + +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.common.collect.ImmutableList; +import google.registry.testing.TestDataHelper; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for the instantiation, marshalling and unmarshalling of {@link EntityFilter}. */ +@RunWith(JUnit4.class) +public class EntityFilterTest { + + private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); + + @Test + public void testEntityFilter_create_nullKinds() { + assertThrows(NullPointerException.class, () -> new EntityFilter(null)); + } + + @Test + public void testEntityFilter_create_emptyKinds() { + assertThrows(IllegalArgumentException.class, () -> new EntityFilter(ImmutableList.of())); + } + + @Test + public void testEntityFilter_marshall() throws IOException { + EntityFilter entityFilter = + new EntityFilter(ImmutableList.of("Registry", "Registrar", "DomainBase")); + assertThat(JSON_FACTORY.toString(entityFilter)) + .isEqualTo(loadJsonString("entity_filter.json").replaceAll("[\\s\\n]+", "")); + } + + @Test + public void testEntityFilter_unmarshall() throws IOException { + EntityFilter entityFilter = loadJson("entity_filter.json", EntityFilter.class); + assertThat(entityFilter.getKinds()) + .containsExactly("Registry", "Registrar", "DomainBase") + .inOrder(); + } + + @Test + public void testEntityFilter_unmarshall_noKinds() throws IOException { + EntityFilter entityFilter = JSON_FACTORY.fromString("{}", EntityFilter.class); + assertThat(entityFilter.getKinds()).isEmpty(); + } + + @Test + public void testEntityFilter_unmarshall_emptyKinds() throws IOException { + EntityFilter entityFilter = JSON_FACTORY.fromString("{ \"kinds\" : [] }", EntityFilter.class); + assertThat(entityFilter.getKinds()).isEmpty(); + } + + private static T loadJson(String fileName, Class type) throws IOException { + return JSON_FACTORY.fromString(loadJsonString(fileName), type); + } + + private static String loadJsonString(String fileName) { + return TestDataHelper.loadFile(EntityFilterTest.class, fileName); + } +} diff --git a/javatests/google/registry/export/datastore/OperationTest.java b/javatests/google/registry/export/datastore/OperationTest.java new file mode 100644 index 000000000..0932aae3f --- /dev/null +++ b/javatests/google/registry/export/datastore/OperationTest.java @@ -0,0 +1,76 @@ +// Copyright 2018 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.export.datastore; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import google.registry.export.datastore.Operation.CommonMetadata; +import google.registry.export.datastore.Operation.Metadata; +import google.registry.export.datastore.Operation.Progress; +import google.registry.testing.TestDataHelper; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for unmarshalling {@link Operation} and its member types. */ +@RunWith(JUnit4.class) +public class OperationTest { + private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); + + @Test + public void testCommonMetadata_unmarshall() throws IOException { + CommonMetadata commonMetadata = loadJson("common_metadata.json", CommonMetadata.class); + assertThat(commonMetadata.getState()).isEqualTo("SUCCESSFUL"); + assertThat(commonMetadata.getOperationType()).isEqualTo("EXPORT_ENTITIES"); + } + + @Test + public void testProgress_unmarshall() throws IOException { + Progress progress = loadJson("progress.json", Progress.class); + assertThat(progress.getWorkCompleted()).isEqualTo(51797); + assertThat(progress.getWorkEstimated()).isEqualTo(54513); + } + + @Test + public void testMetadata_unmarshall() throws IOException { + Metadata metadata = loadJson("metadata.json", Metadata.class); + assertThat(metadata.getCommonMetadata().getOperationType()).isEqualTo("EXPORT_ENTITIES"); + assertThat(metadata.getCommonMetadata().getState()).isEqualTo("SUCCESSFUL"); + } + + @Test + public void testOperation_unmarshall() throws IOException { + Operation operation = loadJson("operation.json", Operation.class); + assertThat(operation.getName()) + .startsWith("projects/domain-registry-alpha/operations/ASAzNjMwOTEyNjUJ"); + assertThat(operation.isProcessing()).isTrue(); + assertThat(operation.isSuccessful()).isFalse(); + assertThat(operation.isDone()).isFalse(); + } + + @Test + public void testOperationList_unmarshall() throws IOException { + Operation.OperationList operationList = + loadJson("operation_list.json", Operation.OperationList.class); + assertThat(operationList.toList()).hasSize(2); + } + + private static T loadJson(String fileName, Class type) throws IOException { + return JSON_FACTORY.fromString(TestDataHelper.loadFile(OperationTest.class, fileName), type); + } +} diff --git a/javatests/google/registry/export/datastore/testdata/common_metadata.json b/javatests/google/registry/export/datastore/testdata/common_metadata.json new file mode 100644 index 000000000..ab77ea33a --- /dev/null +++ b/javatests/google/registry/export/datastore/testdata/common_metadata.json @@ -0,0 +1,6 @@ +{ + "startTime": "2018-10-29T16:01:04.645299Z", + "endTime": "2018-10-29T16:02:19.009859Z", + "operationType": "EXPORT_ENTITIES", + "state": "SUCCESSFUL" +} diff --git a/javatests/google/registry/export/datastore/testdata/entity_filter.json b/javatests/google/registry/export/datastore/testdata/entity_filter.json new file mode 100644 index 000000000..bbc986ba0 --- /dev/null +++ b/javatests/google/registry/export/datastore/testdata/entity_filter.json @@ -0,0 +1,7 @@ +{ + "kinds": [ + "Registry", + "Registrar", + "DomainBase" + ] +} diff --git a/javatests/google/registry/export/datastore/testdata/export_request_content.json b/javatests/google/registry/export/datastore/testdata/export_request_content.json new file mode 100644 index 000000000..23bbee1bc --- /dev/null +++ b/javatests/google/registry/export/datastore/testdata/export_request_content.json @@ -0,0 +1,6 @@ +{ + "entityFilter": { + "kinds": ["Registry", "Registrar", "DomainBase"] + }, + "outputUrlPrefix": "gs://mybucket/path" +} diff --git a/javatests/google/registry/export/datastore/testdata/metadata.json b/javatests/google/registry/export/datastore/testdata/metadata.json new file mode 100644 index 000000000..082d6be92 --- /dev/null +++ b/javatests/google/registry/export/datastore/testdata/metadata.json @@ -0,0 +1,25 @@ +{ + "@type": "type.googleapis.com/google.datastore.admin.v1.ExportEntitiesMetadata", + "common": { + "startTime": "2018-10-29T16:01:04.645299Z", + "endTime": "2018-10-29T16:02:19.009859Z", + "operationType": "EXPORT_ENTITIES", + "state": "SUCCESSFUL" + }, + "progressEntities": { + "workCompleted": "51797", + "workEstimated": "54513" + }, + "progressBytes": { + "workCompleted": "96908367", + "workEstimated": "73773755" + }, + "entityFilter": { + "kinds": [ + "Registry", + "Registrar", + "DomainBase" + ] + }, + "outputUrlPrefix": "gs://domain-registry-alpha-datastore-export-test/2018-10-29T16:01:04_99364" +} diff --git a/javatests/google/registry/export/datastore/testdata/operation.json b/javatests/google/registry/export/datastore/testdata/operation.json new file mode 100644 index 000000000..0d3477253 --- /dev/null +++ b/javatests/google/registry/export/datastore/testdata/operation.json @@ -0,0 +1,19 @@ +{ + "name": "projects/domain-registry-alpha/operations/ASAzNjMwOTEyNjUJ", + "metadata": { + "@type": "type.googleapis.com/google.datastore.admin.v1.ExportEntitiesMetadata", + "common": { + "startTime": "2018-10-29T16:01:04.645299Z", + "operationType": "EXPORT_ENTITIES", + "state": "PROCESSING" + }, + "entityFilter": { + "kinds": [ + "Registry", + "Registrar", + "DomainBase" + ] + }, + "outputUrlPrefix": "gs://domain-registry-alpha-datastore-export-test/2018-10-29T16:01:04_99364" + } +} diff --git a/javatests/google/registry/export/datastore/testdata/operation_list.json b/javatests/google/registry/export/datastore/testdata/operation_list.json new file mode 100644 index 000000000..116774f62 --- /dev/null +++ b/javatests/google/registry/export/datastore/testdata/operation_list.json @@ -0,0 +1,42 @@ +{ + "operations": [ + { + "name": "projects/domain-registry-alpha/operations/ASAzNjMwOTEyNjUJ", + "metadata": { + "@type": "type.googleapis.com/google.datastore.admin.v1.ExportEntitiesMetadata", + "common": { + "startTime": "2018-10-29T16:01:04.645299Z", + "operationType": "EXPORT_ENTITIES", + "state": "PROCESSING" + }, + "entityFilter": { + "kinds": [ + "Registry", + "Registrar", + "DomainBase" + ] + }, + "outputUrlPrefix": "gs://domain-registry-alpha-datastore-export-test/2018-10-29T16:01:04_99364" + } + }, + { + "name": "projects/domain-registry-alpha/operations/ASAzNjMwOTEyNjUJ", + "metadata": { + "@type": "type.googleapis.com/google.datastore.admin.v1.ExportEntitiesMetadata", + "common": { + "startTime": "2018-10-29T16:01:04.645299Z", + "operationType": "EXPORT_ENTITIES", + "state": "PROCESSING" + }, + "entityFilter": { + "kinds": [ + "Registry", + "Registrar", + "DomainBase" + ] + }, + "outputUrlPrefix": "gs://domain-registry-alpha-datastore-export-test/2018-10-29T16:01:04_99364" + } + } + ] +} diff --git a/javatests/google/registry/export/datastore/testdata/progress.json b/javatests/google/registry/export/datastore/testdata/progress.json new file mode 100644 index 000000000..3bf805a0c --- /dev/null +++ b/javatests/google/registry/export/datastore/testdata/progress.json @@ -0,0 +1,4 @@ +{ + "workCompleted": "51797", + "workEstimated": "54513" +}