mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 03:57:51 +02:00
This adds a home in our opensource repo for python libraries and binaries, under a top-level "python" directory. Future CLs will relocate ICANN reporting bits and pieces to new homes under this directory, and will use the MOE configuration and python_directory_import rule defined here. This approach is roughly modeled on the protobuf Bazel opensource project, which also uses a top-level directory for various languages, and also uses the "imports" parameter to exclude that directory in python module names: https://github.com/google/protobuf/blob/v3.0.0-beta-3/BUILD#L568 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=127459882
406 lines
15 KiB
Java
406 lines
15 KiB
Java
// Copyright 2016 The Domain Registry 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 com.google.common.truth.Truth.assert_;
|
|
import static google.registry.testing.DatastoreHelper.persistSimpleResources;
|
|
import static google.registry.util.ResourceUtils.readResourceUtf8;
|
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
import static org.json.XML.toJSONObject;
|
|
|
|
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
|
|
import com.google.appengine.tools.development.testing.LocalModulesServiceTestConfig;
|
|
import com.google.appengine.tools.development.testing.LocalServiceTestConfig;
|
|
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
|
|
import com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig;
|
|
import com.google.appengine.tools.development.testing.LocalURLFetchServiceTestConfig;
|
|
import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig;
|
|
import com.google.common.base.Joiner;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.Sets;
|
|
import com.google.common.io.Files;
|
|
import com.googlecode.objectify.ObjectifyFilter;
|
|
import google.registry.model.ofy.ObjectifyService;
|
|
import google.registry.model.registrar.Registrar;
|
|
import google.registry.model.registrar.Registrar.State;
|
|
import google.registry.model.registrar.RegistrarAddress;
|
|
import google.registry.model.registrar.RegistrarContact;
|
|
import google.registry.util.Clock;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.logging.LogManager;
|
|
import javax.annotation.Nullable;
|
|
import org.json.JSONArray;
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
import org.junit.rules.ExternalResource;
|
|
import org.junit.rules.RuleChain;
|
|
import org.junit.rules.TemporaryFolder;
|
|
import org.junit.rules.TestRule;
|
|
import org.junit.runner.Description;
|
|
import org.junit.runners.model.Statement;
|
|
|
|
/**
|
|
* JUnit Rule for managing the App Engine testing environment.
|
|
*
|
|
* <p>Generally you'll want to configure the environment using only the services you need (because
|
|
* each service is expensive to create).
|
|
*
|
|
* <p>This rule also resets global Objectify for the current thread.
|
|
*
|
|
* @see org.junit.rules.ExternalResource
|
|
*/
|
|
public final class AppEngineRule extends ExternalResource {
|
|
|
|
public static final String NEW_REGISTRAR_GAE_USER_ID = "666";
|
|
public static final String THE_REGISTRAR_GAE_USER_ID = "31337";
|
|
|
|
/**
|
|
* The GAE testing library requires queue.xml to be a file, not a resource in a jar, so we read it
|
|
* in here and write it to a temporary file later.
|
|
*/
|
|
private static final String QUEUE_XML =
|
|
readResourceUtf8("google/registry/env/common/default/WEB-INF/queue.xml");
|
|
|
|
/** A parsed version of the indexes used in the prod code. */
|
|
private static final Set<String> MANUAL_INDEXES = getIndexXmlStrings(
|
|
readResourceUtf8(
|
|
"google/registry/env/common/default/WEB-INF/datastore-indexes.xml"));
|
|
|
|
private static final String LOGGING_PROPERTIES =
|
|
readResourceUtf8(AppEngineRule.class, "logging.properties");
|
|
|
|
private LocalServiceTestHelper helper;
|
|
|
|
/** A rule-within-a-rule to provide a temporary folder for AppEngineRule's internal temp files. */
|
|
private TemporaryFolder temporaryFolder = new TemporaryFolder();
|
|
|
|
private boolean withDatastore;
|
|
private boolean withLocalModules;
|
|
private boolean withTaskQueue;
|
|
private boolean withUserService;
|
|
private boolean withUrlFetch;
|
|
private Clock clock;
|
|
|
|
private String taskQueueXml;
|
|
private UserInfo userInfo;
|
|
|
|
/** Builder for {@link AppEngineRule}. */
|
|
public static class Builder {
|
|
|
|
private AppEngineRule rule = new AppEngineRule();
|
|
|
|
/** Turn on the datastore service. */
|
|
public Builder withDatastore() {
|
|
rule.withDatastore = true;
|
|
return this;
|
|
}
|
|
|
|
/** Turn on the use of local modules. */
|
|
public Builder withLocalModules() {
|
|
rule.withLocalModules = true;
|
|
return this;
|
|
}
|
|
|
|
|
|
/** Turn on the task queue service. */
|
|
public Builder withTaskQueue() {
|
|
return withTaskQueue(QUEUE_XML);
|
|
}
|
|
|
|
/** Turn on the task queue service with a specified set of queues. */
|
|
public Builder withTaskQueue(String taskQueueXml) {
|
|
rule.withTaskQueue = true;
|
|
rule.taskQueueXml = taskQueueXml;
|
|
return this;
|
|
}
|
|
|
|
/** Turn on the URL Fetch service. */
|
|
public Builder withUrlFetch() {
|
|
rule.withUrlFetch = true;
|
|
return this;
|
|
}
|
|
|
|
public Builder withClock(Clock clock) {
|
|
rule.clock = clock;
|
|
return this;
|
|
}
|
|
|
|
public Builder withUserService(UserInfo userInfo) {
|
|
rule.withUserService = true;
|
|
rule.userInfo = userInfo;
|
|
return this;
|
|
}
|
|
|
|
public AppEngineRule build() {
|
|
return rule;
|
|
}
|
|
}
|
|
|
|
public static Builder builder() {
|
|
return new Builder();
|
|
}
|
|
|
|
private static Registrar.Builder makeRegistrarCommon() {
|
|
return new Registrar.Builder()
|
|
.setType(Registrar.Type.REAL)
|
|
.setState(State.ACTIVE)
|
|
.setEmailAddress("new.registrar@example.com")
|
|
.setIcannReferralEmail("lol@sloth.test")
|
|
.setInternationalizedAddress(new RegistrarAddress.Builder()
|
|
.setStreet(ImmutableList.of("123 Example Boulevard"))
|
|
.setCity("Williamsburg")
|
|
.setState("NY")
|
|
.setZip("11211")
|
|
.setCountryCode("US")
|
|
.build())
|
|
.setLocalizedAddress(new RegistrarAddress.Builder()
|
|
.setStreet(ImmutableList.of("123 Example B\u0151ulevard"))
|
|
.setCity("Williamsburg")
|
|
.setState("NY")
|
|
.setZip("11211")
|
|
.setCountryCode("US")
|
|
.build())
|
|
.setPhoneNumber("+1.3334445555")
|
|
.setPhonePasscode("12345")
|
|
.setContactsRequireSyncing(true);
|
|
}
|
|
|
|
/** Public factory for first Registrar to allow comparison against stored value in unit tests. */
|
|
public static Registrar makeRegistrar1() {
|
|
return makeRegistrarCommon()
|
|
.setClientIdentifier("NewRegistrar")
|
|
.setRegistrarName("New Registrar")
|
|
.setIanaIdentifier(8L)
|
|
.setPassword("foo-BAR2")
|
|
.setPhoneNumber("+1.3334445555")
|
|
.setPhonePasscode("12345")
|
|
.build();
|
|
}
|
|
|
|
/** Public factory for second Registrar to allow comparison against stored value in unit tests. */
|
|
public static Registrar makeRegistrar2() {
|
|
return makeRegistrarCommon()
|
|
.setClientIdentifier("TheRegistrar")
|
|
.setRegistrarName("The Registrar")
|
|
.setIanaIdentifier(1L)
|
|
.setPassword("password2")
|
|
.setPhoneNumber("+1.2223334444")
|
|
.setPhonePasscode("22222")
|
|
.build();
|
|
}
|
|
|
|
/**
|
|
* Public factory for first RegistrarContact to allow comparison
|
|
* against stored value in unit tests.
|
|
*/
|
|
public static RegistrarContact makeRegistrarContact1() {
|
|
return new RegistrarContact.Builder()
|
|
.setParent(makeRegistrar1())
|
|
.setName("Jane Doe")
|
|
.setVisibleInWhoisAsAdmin(true)
|
|
.setVisibleInWhoisAsTech(false)
|
|
.setEmailAddress("janedoe@theregistrar.com")
|
|
.setPhoneNumber("+1.1234567890")
|
|
.setTypes(ImmutableSet.of(RegistrarContact.Type.ADMIN))
|
|
.setGaeUserId(NEW_REGISTRAR_GAE_USER_ID)
|
|
.build();
|
|
}
|
|
|
|
/**
|
|
* Public factory for second RegistrarContact to allow comparison
|
|
* against stored value in unit tests.
|
|
*/
|
|
public static RegistrarContact makeRegistrarContact2() {
|
|
return new RegistrarContact.Builder()
|
|
.setParent(makeRegistrar2())
|
|
.setName("John Doe")
|
|
.setEmailAddress("johndoe@theregistrar.com")
|
|
.setPhoneNumber("+1.1234567890")
|
|
.setTypes(ImmutableSet.of(RegistrarContact.Type.ADMIN))
|
|
.setGaeUserId(THE_REGISTRAR_GAE_USER_ID)
|
|
.build();
|
|
}
|
|
|
|
/** Hack to make sure AppEngineRule is always wrapped in a TemporaryFolder rule. */
|
|
@Override
|
|
public Statement apply(Statement base, Description description) {
|
|
return RuleChain.outerRule(temporaryFolder).around(new TestRule() {
|
|
@Override
|
|
public Statement apply(Statement base, Description description) {
|
|
return AppEngineRule.super.apply(base, null);
|
|
}}).apply(base, description);
|
|
}
|
|
|
|
@Override
|
|
protected void before() throws IOException {
|
|
setupLogging();
|
|
Set<LocalServiceTestConfig> configs = new HashSet<>();
|
|
if (withUrlFetch) {
|
|
configs.add(new LocalURLFetchServiceTestConfig());
|
|
}
|
|
if (withDatastore) {
|
|
configs.add(new LocalDatastoreServiceTestConfig()
|
|
// We need to set this to allow cross entity group transactions.
|
|
.setApplyAllHighRepJobPolicy()
|
|
// This causes unit tests to write a file containing any indexes the test required. We
|
|
// can use that file below to make sure we have the right indexes in our prod code.
|
|
.setNoIndexAutoGen(false));
|
|
// This forces app engine to write the generated indexes to a usable location.
|
|
System.setProperty("appengine.generated.dir", temporaryFolder.getRoot().getAbsolutePath());
|
|
}
|
|
if (withLocalModules) {
|
|
configs.add(new LocalModulesServiceTestConfig()
|
|
.addDefaultModuleVersion()
|
|
.addAutomaticScalingModuleVersion("default", "v1")
|
|
.addAutomaticScalingModuleVersion("tools", "v1")
|
|
.addAutomaticScalingModuleVersion("backend", "v1"));
|
|
}
|
|
if (withTaskQueue) {
|
|
File queueFile = temporaryFolder.newFile("queue.xml");
|
|
Files.write(taskQueueXml, queueFile, UTF_8);
|
|
configs.add(new LocalTaskQueueTestConfig()
|
|
.setQueueXmlPath(queueFile.getAbsolutePath()));
|
|
}
|
|
if (withUserService) {
|
|
configs.add(new LocalUserServiceTestConfig());
|
|
}
|
|
|
|
helper = new LocalServiceTestHelper(configs.toArray(new LocalServiceTestConfig[]{}));
|
|
|
|
if (withUserService) {
|
|
// Set top-level properties on LocalServiceTestConfig for user login.
|
|
helper.setEnvIsLoggedIn(userInfo.isLoggedIn())
|
|
// This envAttributes thing is the only way to set userId.
|
|
// see https://code.google.com/p/googleappengine/issues/detail?id=3579
|
|
.setEnvAttributes(ImmutableMap.<String, Object>of(
|
|
"com.google.appengine.api.users.UserService.user_id_key", userInfo.gaeUserId()))
|
|
.setEnvAuthDomain(userInfo.authDomain())
|
|
.setEnvEmail(userInfo.email())
|
|
.setEnvIsAdmin(userInfo.isAdmin());
|
|
}
|
|
|
|
if (clock != null) {
|
|
helper.setClock(new com.google.appengine.tools.development.Clock() {
|
|
@Override
|
|
public long getCurrentTime() {
|
|
return clock.nowUtc().getMillis();
|
|
}
|
|
});
|
|
}
|
|
|
|
helper.setUp();
|
|
|
|
if (withDatastore) {
|
|
ObjectifyService.initOfy();
|
|
// Reset id allocation in ObjectifyService so that ids are deterministic in tests.
|
|
ObjectifyService.resetNextTestId();
|
|
loadInitialData();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void after() {
|
|
// Resets Objectify. Although it would seem more obvious to do this at the start of a request
|
|
// instead of at the end, this is more consistent with what ObjectifyFilter does in real code.
|
|
ObjectifyFilter.complete();
|
|
helper.tearDown();
|
|
helper = null;
|
|
// Test that the datastore didn't need any indexes we don't have listed in our index file.
|
|
try {
|
|
Set<String> autoIndexes = getIndexXmlStrings(Files.toString(
|
|
new File(temporaryFolder.getRoot(), "datastore-indexes-auto.xml"), UTF_8));
|
|
Set<String> missingIndexes = Sets.difference(autoIndexes, MANUAL_INDEXES);
|
|
if (!missingIndexes.isEmpty()) {
|
|
assert_().fail("Missing indexes:\n%s", Joiner.on('\n').join(missingIndexes));
|
|
}
|
|
} catch (IOException e) { // This is fine; no indexes were written.
|
|
}
|
|
}
|
|
|
|
/** Install {@code testing/logging.properties} so logging is less noisy. */
|
|
private static void setupLogging() throws IOException {
|
|
LogManager.getLogManager()
|
|
.readConfiguration(new ByteArrayInputStream(LOGGING_PROPERTIES.getBytes(UTF_8)));
|
|
}
|
|
|
|
/** Read a datastore index file, and parse the indexes into individual strings. */
|
|
private static Set<String> getIndexXmlStrings(String indexFile) {
|
|
ImmutableSet.Builder<String> builder = new ImmutableSet.Builder<>();
|
|
try {
|
|
// To normalize the indexes, we are going to pass them through JSON and then rewrite the xml.
|
|
JSONObject datastoreIndexes = new JSONObject();
|
|
Object indexes = toJSONObject(indexFile).get("datastore-indexes");
|
|
if (indexes instanceof JSONObject) {
|
|
datastoreIndexes = (JSONObject) indexes;
|
|
}
|
|
for (JSONObject index : getJsonAsArray(datastoreIndexes.opt("datastore-index"))) {
|
|
builder.add(getIndexXmlString(index));
|
|
}
|
|
} catch (JSONException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
return builder.build();
|
|
}
|
|
|
|
/**
|
|
* Normalize a value from JSONObject that represents zero, one, or many values. If there were zero
|
|
* values this will be null or an empty JSONArray, depending on how the field was represented in
|
|
* JSON. If there was one value, the object passed in will be that value. If there were more than
|
|
* one values, the object will be a JSONArray containing those values. We will return a list in
|
|
* all cases.
|
|
*/
|
|
private static List<JSONObject> getJsonAsArray(@Nullable Object object) throws JSONException {
|
|
ImmutableList.Builder<JSONObject> builder = new ImmutableList.Builder<>();
|
|
if (object instanceof JSONArray) {
|
|
for (int i = 0; i < ((JSONArray) object).length(); ++i) {
|
|
builder.add(((JSONArray) object).getJSONObject(i));
|
|
}
|
|
} else if (object instanceof JSONObject){
|
|
// When there's only a single entry it won't be wrapped in an array.
|
|
builder.add((JSONObject) object);
|
|
}
|
|
return builder.build();
|
|
}
|
|
|
|
/** Turn a JSON representation of an index into xml. */
|
|
private static String getIndexXmlString(JSONObject source) throws JSONException {
|
|
StringBuilder builder = new StringBuilder();
|
|
builder.append(String.format(
|
|
"<datastore-index kind=\"%s\" ancestor=\"%s\" source=\"manual\">\n",
|
|
source.getString("kind"),
|
|
source.get("ancestor").toString()));
|
|
for (JSONObject property : getJsonAsArray(source.get("property"))) {
|
|
builder.append(String.format(
|
|
" <property name=\"%s\" direction=\"%s\"/>\n",
|
|
property.getString("name"),
|
|
property.getString("direction")));
|
|
}
|
|
return builder.append("</datastore-index>").toString();
|
|
}
|
|
|
|
/** Create some fake registrars. */
|
|
public static void loadInitialData() {
|
|
persistSimpleResources(
|
|
ImmutableList.of(
|
|
makeRegistrar1(), makeRegistrarContact1(), makeRegistrar2(), makeRegistrarContact2()));
|
|
}
|
|
}
|