Import code from internal repository to git

This commit is contained in:
Justine Tunney 2016-03-01 17:18:14 -05:00
commit 0ef0c933d2
2490 changed files with 281594 additions and 0 deletions

View file

@ -0,0 +1,64 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import com.google.common.collect.ImmutableSet;
import com.google.common.truth.FailureStrategy;
import com.google.domain.registry.model.domain.DomainBase;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.model.domain.launch.LaunchNotice;
import com.google.domain.registry.model.domain.secdns.DelegationSignerData;
import com.google.domain.registry.model.eppcommon.AuthInfo;
import com.google.domain.registry.testing.TruthChainer.And;
import java.util.Set;
/** Truth subject for asserting things about {@link DomainResource} instances. */
public abstract class AbstractDomainBaseSubject
<T extends DomainBase, S extends AbstractDomainBaseSubject<T, S>>
extends AbstractEppResourceSubject<T, S> {
public AbstractDomainBaseSubject(FailureStrategy strategy, T subject) {
super(strategy, subject);
}
public And<S> hasFullyQualifiedDomainName(String fullyQualifiedDomainName) {
return hasValue(
fullyQualifiedDomainName,
getSubject().getFullyQualifiedDomainName(),
"has fullyQualifiedDomainName");
}
public And<S> hasExactlyDsData(DelegationSignerData... dsData) {
return hasExactlyDsData(ImmutableSet.copyOf(dsData));
}
public And<S> hasExactlyDsData(Set<DelegationSignerData> dsData) {
return hasValue(dsData, getSubject().getDsData(), "has dsData");
}
public And<S> hasNumDsData(int num) {
return hasValue(num, getSubject().getDsData().size(), "has num dsData");
}
public And<S> hasLaunchNotice(LaunchNotice launchNotice) {
return hasValue(launchNotice, getSubject().getLaunchNotice(), "has launchNotice");
}
public And<S> hasAuthInfoPwd(String pw) {
AuthInfo authInfo = getSubject().getAuthInfo();
return hasValue(pw, authInfo == null ? null : authInfo.getPw().getValue(), "has auth info pw");
}
}

View file

@ -0,0 +1,259 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertThat;
import static com.google.domain.registry.model.EppResourceUtils.isActive;
import static com.google.domain.registry.testing.DatastoreHelper.getHistoryEntriesOfType;
import static com.google.domain.registry.testing.HistoryEntrySubject.assertAboutHistoryEntries;
import com.google.common.collect.ImmutableSet;
import com.google.common.truth.FailureStrategy;
import com.google.common.truth.Subject;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.eppcommon.StatusValue;
import com.google.domain.registry.model.eppcommon.Trid;
import com.google.domain.registry.model.reporting.HistoryEntry;
import com.google.domain.registry.model.transfer.TransferStatus;
import com.google.domain.registry.testing.TruthChainer.And;
import com.google.domain.registry.testing.TruthChainer.Which;
import org.joda.time.DateTime;
import java.util.List;
import java.util.Objects;
/** Base Truth subject for asserting things about epp resources. */
abstract class AbstractEppResourceSubject
<T extends EppResource, S extends AbstractEppResourceSubject<T, S>> extends Subject<S, T> {
public AbstractEppResourceSubject(FailureStrategy strategy, T subject) {
super(strategy, checkNotNull(subject));
}
private List<HistoryEntry> getHistoryEntries() {
return DatastoreHelper.getHistoryEntries(getSubject());
}
@SuppressWarnings("unchecked")
protected And<S> andChainer() {
return new And<S>((S) this);
}
@Override
public String getDisplaySubject() {
return String.format(
"%s with foreign key '%s'",
getSubject().getClass().getSimpleName(),
getSubject().getForeignKey());
}
public And<S> hasRepoId(long roid) {
return hasValue(roid, getSubject().getRepoId(), "has repoId");
}
public And<S> hasNoHistoryEntries() {
if (!getHistoryEntries().isEmpty()) {
fail("has no history entries");
}
return andChainer();
}
public And<S> hasNumHistoryEntries(int num) {
if (getHistoryEntries().size() != num) {
failWithBadResults("has this number of history entries", num, getHistoryEntries().size());
}
return andChainer();
}
public And<S> hasNumHistoryEntriesOfType(HistoryEntry.Type type, int num) {
List<HistoryEntry> entries = getHistoryEntriesOfType(getSubject(), type);
if (entries.size() != num) {
failWithBadResults(
String.format("has this number of history entries of type %s", type.toString()),
num,
entries.size());
}
return andChainer();
}
public And<S> hasOneHistoryEntryEachOfTypes(HistoryEntry.Type ... types) {
hasNumHistoryEntries(types.length);
for (HistoryEntry.Type type : types) {
hasNumHistoryEntriesOfType(type, 1);
}
return andChainer();
}
public And<S> hasOnlyOneHistoryEntry() {
int numHistoryEntries = getHistoryEntries().size();
if (numHistoryEntries != 1) {
fail(String.format("has exactly one history entry (it has %d)", numHistoryEntries));
}
return andChainer();
}
public HistoryEntrySubject hasOnlyOneHistoryEntryWhich() {
hasOnlyOneHistoryEntry();
return assertAboutHistoryEntries().that(getHistoryEntries().get(0)).withCustomDisplaySubject(
"the only history entry for " + getDisplaySubject());
}
public Which<HistoryEntrySubject> hasHistoryEntryAtIndex(int index) {
List<HistoryEntry> historyEntries = getHistoryEntries();
if (historyEntries.size() < index + 1) {
failWithBadResults(
"has at least number of history entries", index + 1, historyEntries.size());
}
return new Which<HistoryEntrySubject>(assertAboutHistoryEntries()
.that(getHistoryEntries().get(index)).withCustomDisplaySubject(String.format(
"the history entry for %s at index %s", getDisplaySubject(), index)));
}
public And<S> hasStatusValue(StatusValue statusValue) {
if (!getSubject().getStatusValues().contains(statusValue)) {
failWithRawMessage("%s should have had status value %s", getDisplaySubject(), statusValue);
}
return andChainer();
}
public And<S> doesNotHaveStatusValue(StatusValue statusValue) {
if (getSubject().getStatusValues().contains(statusValue)) {
failWithRawMessage(
"%s should not have had status value %s", getDisplaySubject(), statusValue);
}
return andChainer();
}
public And<S> hasExactlyStatusValues(StatusValue... statusValues) {
if (!ImmutableSet.copyOf(getSubject().getStatusValues())
.equals(ImmutableSet.copyOf(statusValues))) {
assertThat(getSubject().getStatusValues()).named("status values for " + getDisplaySubject())
.containsExactly((Object[]) statusValues);
}
return andChainer();
}
public And<S> hasDeletionTime(DateTime deletionTime) {
return hasValue(
deletionTime,
getSubject().getDeletionTime(),
"has deletionTime");
}
public And<S> hasLastTransferTime(DateTime lastTransferTime) {
return hasValue(
lastTransferTime,
getSubject().getLastTransferTime(),
"has lastTransferTime");
}
public And<S> hasLastTransferTimeNotEqualTo(DateTime lastTransferTime) {
return doesNotHaveValue(
lastTransferTime,
getSubject().getLastTransferTime(),
"lastTransferTime");
}
public And<S> hasLastEppUpdateTime(DateTime lastUpdateTime) {
return hasValue(
lastUpdateTime,
getSubject().getLastEppUpdateTime(),
"has lastEppUpdateTime");
}
public And<S> hasLastEppUpdateTimeAtLeast(DateTime before) {
DateTime lastEppUpdateTime = getSubject().getLastEppUpdateTime();
if (lastEppUpdateTime == null || before.isAfter(lastEppUpdateTime)) {
failWithBadResults("has lastEppUpdateTime at least", before, lastEppUpdateTime);
}
return andChainer();
}
public And<S> hasLastEppUpdateClientId(String clientId) {
return hasValue(
clientId,
getSubject().getLastEppUpdateClientId(),
"has lastEppUpdateClientId");
}
public And<S> hasCurrentSponsorClientId(String clientId) {
return hasValue(
clientId,
getSubject().getCurrentSponsorClientId(),
"has currentSponsorClientId");
}
public And<S> hasTransferStatus(TransferStatus transferStatus) {
return hasValue(
transferStatus,
getSubject().getTransferData().getTransferStatus(),
"has transferStatus");
}
public And<S> hasTransferRequestTrid(Trid trid) {
return hasValue(
trid,
getSubject().getTransferData().getTransferRequestTrid(),
"has trid");
}
public And<S> hasPendingTransferExpirationTime(DateTime pendingTransferExpirationTime) {
return hasValue(
pendingTransferExpirationTime,
getSubject().getTransferData().getPendingTransferExpirationTime(),
"has pendingTransferExpirationTime");
}
public And<S> hasTransferGainingClientId(String gainingClientId) {
return hasValue(
gainingClientId,
getSubject().getTransferData().getGainingClientId(),
"has transfer ga");
}
public And<S> hasTransferLosingClientId(String losingClientId) {
return hasValue(
losingClientId,
getSubject().getTransferData().getLosingClientId(),
"has transfer losingClientId");
}
public And<S> isActiveAt(DateTime time) {
if (!isActive(getSubject(), time)) {
fail("is active at " + time);
}
return andChainer();
}
protected void failWithBadResults(String dualVerb, Object expected, Object actual) {
failWithBadResults(dualVerb, expected, dualVerb, actual);
}
protected <E> And<S> hasValue(E expected, E actual, String verb) {
if (!Objects.equals(expected, actual)) {
failWithBadResults(verb, expected, actual);
}
return andChainer();
}
protected <E> And<S> doesNotHaveValue(E badValue, E actual, String valueName) {
if (Objects.equals(badValue, actual)) {
fail("has " + valueName + " not equal to " + badValue);
}
return andChainer();
}
}

View file

@ -0,0 +1,409 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.truth.Truth.assert_;
import static com.google.domain.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.google.domain.registry.model.ofy.ObjectifyService;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registrar.Registrar.State;
import com.google.domain.registry.model.registrar.RegistrarAddress;
import com.google.domain.registry.model.registrar.RegistrarContact;
import com.google.domain.registry.util.Clock;
import com.googlecode.objectify.ObjectifyFilter;
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;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.LogManager;
import javax.annotation.Nullable;
/**
* 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("com/google/domain/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(
"com/google/domain/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.
for (JSONObject index : getJsonAsArray(toJSONObject(indexFile)
.getJSONObject("datastore-indexes")
.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.getString("ancestor")));
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() {
DatastoreHelper.persistSimpleGlobalResources(Arrays.asList(
makeRegistrar1(),
makeRegistrarContact1(),
makeRegistrar2(),
makeRegistrarContact2()));
}
}

View file

@ -0,0 +1,56 @@
package(
default_visibility = ["//java/com/google/domain/registry:registry_project"],
)
java_library(
name = "testing",
srcs = glob(
["*.java"],
exclude = [
"FakeProxyConnectionMetadataProvider.java",
"FakeQuotaServerClient.java",
],
),
resources = [
"logging.properties",
"//java/com/google/domain/registry/env/common/default:WEB-INF/datastore-indexes.xml",
"//java/com/google/domain/registry/env/common/default:WEB-INF/queue.xml",
] + glob(["*.csv"]),
deps = [
"//java/com/google/common/base",
"//java/com/google/common/cache",
"//java/com/google/common/collect",
"//java/com/google/common/io",
"//java/com/google/common/net",
"//java/com/google/common/util/concurrent",
"//java/com/google/domain/registry/config",
"//java/com/google/domain/registry/dns:constants",
"//java/com/google/domain/registry/flows",
"//java/com/google/domain/registry/model",
"//java/com/google/domain/registry/request",
"//java/com/google/domain/registry/tmch",
"//java/com/google/domain/registry/util",
"//java/com/google/domain/registry/xml",
"//third_party/java/appengine:appengine-api-testonly",
"//third_party/java/appengine:appengine-integration-testing",
"//third_party/java/appengine:appengine-stubs",
"//third_party/java/appengine:appengine-testing",
"//third_party/java/appengine_gcs_client",
"//third_party/java/auto:auto_value",
"//third_party/java/bouncycastle",
"//third_party/java/bouncycastle_bcpg",
"//third_party/java/joda_money",
"//third_party/java/joda_time",
"//third_party/java/json",
"//third_party/java/json_simple",
"//third_party/java/jsr305_annotations",
"//third_party/java/jsr330_inject",
"//third_party/java/junit",
"//third_party/java/mockito",
"//third_party/java/objectify:objectify-v4_1",
"//third_party/java/servlet/servlet_api",
"//third_party/java/truth",
],
)

View file

@ -0,0 +1,43 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.rules.ExternalResource;
import java.security.Security;
/**
* JUnit Rule for registering {@link BouncyCastleProvider} with Java Security.
*
* <p>This rule is necessary in order to use the {@code "BC"} provider of cryptographic functions.
* Normally you would perform this registration in your {@code main()} function.
*
* @see BouncyCastleProvider
* @see org.junit.rules.ExternalResource
* @see java.security.Security#addProvider(java.security.Provider)
*/
public class BouncyCastleProviderRule extends ExternalResource {
@Override
protected void before() throws Exception {
Security.addProvider(new BouncyCastleProvider());
}
@Override
protected void after() {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
}
}

View file

@ -0,0 +1,79 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
/** Utility methods for tests that involve certificates. */
public final class CertificateSamples {
// openssl req -new -nodes -x509 -days 10000 -newkey rsa:2048 -keyout client1.key -out client1.crt -subj "/C=US/ST=New York/L=New York/O=Google/OU=domain-registry-test/CN=client1"
public static final String SAMPLE_CERT = ""
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIDvTCCAqWgAwIBAgIJAK/PgPT0jTwRMA0GCSqGSIb3DQEBCwUAMHUxCzAJBgNV\n"
+ "BAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3IFlvcmsxDzAN\n"
+ "BgNVBAoMBkdvb2dsZTEdMBsGA1UECwwUZG9tYWluLXJlZ2lzdHJ5LXRlc3QxEDAO\n"
+ "BgNVBAMMB2NsaWVudDEwHhcNMTUwODI2MTkxODA4WhcNNDMwMTExMTkxODA4WjB1\n"
+ "MQswCQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxETAPBgNVBAcMCE5ldyBZ\n"
+ "b3JrMQ8wDQYDVQQKDAZHb29nbGUxHTAbBgNVBAsMFGRvbWFpbi1yZWdpc3RyeS10\n"
+ "ZXN0MRAwDgYDVQQDDAdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
+ "CgKCAQEAvoE/IoFJyzb0dU4NFhL8FYgy+B/GnUd5aA66CMx5xKRMbEAtIgxU8TTO\n"
+ "W+9jdTsE00Grk3Ct4KdY73CYW+6IFXL4O0K/m5S+uajh+I2UMVZJV38RAIqNxue0\n"
+ "Egv9M4haSsCVIPcX9b+6McywfYSF1bzPb2Gb2FAQO7Jb0BjlPhPMIROCrbG40qPg\n"
+ "LWrl33dz+O52kO+DyZEzHqI55xH6au77sMITsJe+X23lzQcMFUUm8moiOw0EKrj/\n"
+ "GaMTZLHP46BCRoJDAPTNx55seIwgAHbKA2VVtqrvmA2XYJQA6ipdhfKRoJFy8Z8H\n"
+ "DYsorGtazQL2HhF/5uJD25z1m5eQHQIDAQABo1AwTjAdBgNVHQ4EFgQUParEmiSR\n"
+ "U/Oqy8hr7k+MBKhZwVkwHwYDVR0jBBgwFoAUParEmiSRU/Oqy8hr7k+MBKhZwVkw\n"
+ "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAojsUhF6PtZrStnHBFWNR\n"
+ "ryzvANB8krZlYeX9Hkqn8zIVfAkpbVmL8aZQ7yj17jSpw47PQh3x5gwA9yc/SS0G\n"
+ "E1rGuxYH02UGbua8G0+vviSQfLtskPQzK7EIR63WNhHEo/Q9umLJkZ0LguWEBf3L\n"
+ "q8CoXv2i/RNvqVPcTNp/zCKXJZAa8wAjNRJs834AZj4k5xwyYZ3F8D5PGz+YMOmV\n"
+ "M9Qd+NdXSC/Qn7HQzFhE8p5elBV35P8oX5dXEfn0S7zOXDenp5JvvLoggOWOcKsq\n"
+ "KiWDQrsT+TMKmHL94/h4t7FghtQLMzY5SGYJsYTv/LG8tewrz6KRb/Wj3JNojyEw\n"
+ "Ug==\n"
+ "-----END CERTIFICATE-----\n";
// python -c "import sys;print sys.argv[1].decode('hex').encode('base64').strip('\n=')" $(openssl x509 -fingerprint -sha256 -in client1.cert | grep -Po '(?<=Fingerprint=).*' | sed s/://g)
public static final String SAMPLE_CERT_HASH = "vue+ZFJC2R7/LedIDQ53NbMoIMSVpqjEJA1CAJVumos";
// openssl req -new -nodes -x509 -days 10000 -newkey rsa:2048 -keyout client2.key -out client2.crt -subj "/C=US/ST=New York/L=New York/O=Google/OU=domain-registry-test/CN=client2"
public static final String SAMPLE_CERT2 = ""
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIDvTCCAqWgAwIBAgIJANoEy6mYwalPMA0GCSqGSIb3DQEBCwUAMHUxCzAJBgNV\n"
+ "BAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3IFlvcmsxDzAN\n"
+ "BgNVBAoMBkdvb2dsZTEdMBsGA1UECwwUZG9tYWluLXJlZ2lzdHJ5LXRlc3QxEDAO\n"
+ "BgNVBAMMB2NsaWVudDIwHhcNMTUwODI2MTkyODU3WhcNNDMwMTExMTkyODU3WjB1\n"
+ "MQswCQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxETAPBgNVBAcMCE5ldyBZ\n"
+ "b3JrMQ8wDQYDVQQKDAZHb29nbGUxHTAbBgNVBAsMFGRvbWFpbi1yZWdpc3RyeS10\n"
+ "ZXN0MRAwDgYDVQQDDAdjbGllbnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
+ "CgKCAQEAw2FtuDyoR+rUJHp6k7KwaoHGHPV1xnC8IpG9O0SZubOXrFrnBHggBsbu\n"
+ "+DsknbHXjmoihSFFem0KQqJg5y34aDAHXQV3iqa7nDfb1x4oc5voVz9gqjdmGKNm\n"
+ "WF4MTIPNMu8KY52M852mMCxODK+6MZYp7wCmVa63KdCm0bW/XsLgoA/+FVGwKLhf\n"
+ "UqFzt10Cf+87zl4VHrSaJqcHBYM6yAO5lvkr5VC6g8rRQ+dJ+pBT2D99YpSF1aFc\n"
+ "rWbBreIypixZAnXm/Xoogu6RnohS29VCJp2dXFAJmKXGwyKNQFXfEKxZBaBi8uKH\n"
+ "XF459795eyF9xHgSckEgu7jZlxOk6wIDAQABo1AwTjAdBgNVHQ4EFgQUv26AsQyc\n"
+ "kLOjkhqcFLOuueB33l4wHwYDVR0jBBgwFoAUv26AsQyckLOjkhqcFLOuueB33l4w\n"
+ "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANBuV+QDISSnGAEHKbR40\n"
+ "zUYdOjdZ399zcFNqTSPHwmE0Qu8pbmXhofpBfjzrcv0tkVbhSLYnT22qhx7aDmhb\n"
+ "bOS8CeVYCwl5eiDTkJly3pRZLzJpy+UT5z8SPxO3MrTqn+wuj0lBpWRTBCWYAUpr\n"
+ "IFRmgVB3IwVb60UIuxhmuk8TVss2SzNrdhdt36eAIPJ0RWEb0KHYHi35Y6lt4f+t\n"
+ "iVk+ZR0cCbHUs7Q1RqREXHd/ICuMRLY/MsadVQ9WDqVOridh198X/OIqdx/p9kvJ\n"
+ "1R80jDcVGNhYVXLmHu4ho4xrOaliSYvUJSCmaaSEGVZ/xE5PI7S6A8RMdj0iXLSt\n"
+ "Bg==\n"
+ "-----END CERTIFICATE-----\n";
// python -c "import sys;print sys.argv[1].decode('hex').encode('base64').strip('\n=')" $(openssl x509 -fingerprint -sha256 -in client2.crt | grep -Po '(?<=Fingerprint=).*' | sed s/://g)
public static final String SAMPLE_CERT2_HASH = "GNd6ZP8/n91t9UTnpxR8aH7aAW4+CpvufYx9ViGbcMY";
private CertificateSamples() {}
}

View file

@ -0,0 +1,130 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.AbstractVerb.DelegatedVerb;
import com.google.common.truth.FailureStrategy;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.contact.PostalInfo;
import com.google.domain.registry.model.eppcommon.AuthInfo;
import com.google.domain.registry.testing.TruthChainer.And;
/** Truth subject for asserting things about {@link ContactResource} instances. */
public final class ContactResourceSubject
extends AbstractEppResourceSubject<ContactResource, ContactResourceSubject> {
/** A factory for instances of this subject. */
private static class SubjectFactory
extends ReflectiveSubjectFactory<ContactResource, ContactResourceSubject>{}
public ContactResourceSubject(FailureStrategy strategy, ContactResource subject) {
super(strategy, checkNotNull(subject));
}
public And<ContactResourceSubject> hasLocalizedPostalInfo(PostalInfo postalInfo) {
return hasValue(postalInfo, getSubject().getLocalizedPostalInfo(), "has localizedPostalInfo");
}
public And<ContactResourceSubject> hasNullLocalizedPostalInfo() {
if (getSubject().getLocalizedPostalInfo() != null) {
fail("has null localized postal info");
}
return andChainer();
}
public And<ContactResourceSubject> hasNonNullLocalizedPostalInfo() {
if (getSubject().getLocalizedPostalInfo() == null) {
fail("has non-null localized postal info");
}
return andChainer();
}
public And<ContactResourceSubject> hasInternationalizedPostalInfo(
PostalInfo postalInfo) {
return hasValue(
postalInfo,
getSubject().getInternationalizedPostalInfo(),
"has internationalizedPostalInfo");
}
public And<ContactResourceSubject> hasNullInternationalizedPostalInfo() {
if (getSubject().getInternationalizedPostalInfo() != null) {
fail("has null internationalized postal info");
}
return andChainer();
}
public And<ContactResourceSubject> hasNonNullInternationalizedPostalInfo() {
if (getSubject().getInternationalizedPostalInfo() == null) {
fail("has non-null internationalized postal info");
}
return andChainer();
}
public And<ContactResourceSubject> hasNullEmailAddress() {
if (getSubject().getEmailAddress() != null) {
fail("has null email address");
}
return andChainer();
}
public And<ContactResourceSubject> hasNonNullEmailAddress() {
if (getSubject().getEmailAddress() == null) {
fail("has non-null email address");
}
return andChainer();
}
public And<ContactResourceSubject> hasNullVoiceNumber() {
if (getSubject().getVoiceNumber() != null) {
fail("has null voice number");
}
return andChainer();
}
public And<ContactResourceSubject> hasNonNullVoiceNumber() {
if (getSubject().getVoiceNumber() == null) {
fail("has non-null voice number");
}
return andChainer();
}
public And<ContactResourceSubject> hasNullFaxNumber() {
if (getSubject().getFaxNumber() != null) {
fail("has null fax number");
}
return andChainer();
}
public And<ContactResourceSubject> hasNonNullFaxNumber() {
if (getSubject().getFaxNumber() == null) {
fail("has non-null fax number");
}
return andChainer();
}
public And<ContactResourceSubject> hasAuthInfoPwd(String pw) {
AuthInfo authInfo = getSubject().getAuthInfo();
return hasValue(pw, authInfo == null ? null : authInfo.getPw().getValue(), "has auth info pw");
}
public static DelegatedVerb<ContactResourceSubject, ContactResource> assertAboutContacts() {
return assertAbout(new SubjectFactory());
}
}

View file

@ -0,0 +1,885 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Suppliers.memoize;
import static com.google.common.collect.Iterables.toArray;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.domain.registry.flows.ResourceFlowUtils.createTransferResponse;
import static com.google.domain.registry.model.EppResourceUtils.createContactHostRoid;
import static com.google.domain.registry.model.EppResourceUtils.createDomainRoid;
import static com.google.domain.registry.model.domain.DomainUtils.getTldFromDomainName;
import static com.google.domain.registry.model.domain.launch.ApplicationStatus.VALIDATED;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.CollectionUtils.difference;
import static com.google.domain.registry.util.CollectionUtils.union;
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
import static com.google.domain.registry.util.DateTimeUtils.START_OF_TIME;
import static com.google.domain.registry.util.DomainNameUtils.ACE_PREFIX_REGEX;
import static com.google.domain.registry.util.ResourceUtils.readResourceUtf8;
import static java.util.Arrays.asList;
import static org.joda.money.CurrencyUnit.USD;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.domain.registry.config.RegistryEnvironment;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.EppResource.ForeignKeyedEppResource;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.billing.BillingEvent;
import com.google.domain.registry.model.billing.BillingEvent.Reason;
import com.google.domain.registry.model.contact.ContactAuthInfo;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.domain.DesignatedContact;
import com.google.domain.registry.model.domain.DesignatedContact.Type;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.domain.DomainAuthInfo;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.model.domain.ReferenceUnion;
import com.google.domain.registry.model.domain.launch.LaunchPhase;
import com.google.domain.registry.model.eppcommon.AuthInfo.PasswordAuth;
import com.google.domain.registry.model.eppcommon.StatusValue;
import com.google.domain.registry.model.eppcommon.Trid;
import com.google.domain.registry.model.host.HostResource;
import com.google.domain.registry.model.index.DomainApplicationIndex;
import com.google.domain.registry.model.index.EppResourceIndex;
import com.google.domain.registry.model.index.ForeignKeyIndex;
import com.google.domain.registry.model.ofy.ObjectifyService;
import com.google.domain.registry.model.poll.PollMessage;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.model.registry.Registry.TldState;
import com.google.domain.registry.model.registry.label.PremiumList;
import com.google.domain.registry.model.registry.label.ReservedList;
import com.google.domain.registry.model.reporting.HistoryEntry;
import com.google.domain.registry.model.smd.EncodedSignedMark;
import com.google.domain.registry.model.transfer.TransferData;
import com.google.domain.registry.model.transfer.TransferData.Builder;
import com.google.domain.registry.model.transfer.TransferData.TransferServerApproveEntity;
import com.google.domain.registry.model.transfer.TransferStatus;
import com.google.domain.registry.tmch.LordnTask;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.VoidWork;
import com.googlecode.objectify.Work;
import com.googlecode.objectify.cmd.Saver;
import org.joda.money.Money;
import org.joda.time.DateTime;
import java.util.List;
/** Static utils for setting up test resources. */
public class DatastoreHelper {
private static final Supplier<String[]> DEFAULT_PREMIUM_LIST_CONTENTS =
memoize(new Supplier<String[]>() {
@Override
public String[] get() {
return toArray(
Splitter.on('\n').split(
readResourceUtf8(DatastoreHelper.class, "default_premium_list_testdata.csv")),
String.class);
}});
public static HostResource newHostResource(String hostName) {
return new HostResource.Builder()
.setFullyQualifiedHostName(hostName)
.setCurrentSponsorClientId("TheRegistrar")
.setCreationTimeForTest(START_OF_TIME)
.setRepoId(generateNewContactHostRoid())
.build();
}
public static DomainResource newDomainResource(String domainName) {
String repoId = generateNewDomainRoid(getTldFromDomainName(domainName));
return newDomainResource(domainName, repoId, persistActiveContact("contact1234"));
}
public static DomainResource newDomainResource(String domainName, ContactResource contact) {
return newDomainResource(
domainName, generateNewDomainRoid(getTldFromDomainName(domainName)), contact);
}
public static DomainResource newDomainResource(
String domainName, String repoId, ContactResource contact) {
return new DomainResource.Builder()
.setRepoId(repoId)
.setFullyQualifiedDomainName(domainName)
.setCreationClientId("TheRegistrar")
.setCurrentSponsorClientId("TheRegistrar")
.setCreationTimeForTest(START_OF_TIME)
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("2fooBAR")))
.setRegistrant(ReferenceUnion.create(contact))
.setContacts(ImmutableSet.of(
DesignatedContact.create(Type.ADMIN, ReferenceUnion.create(contact)),
DesignatedContact.create(Type.TECH, ReferenceUnion.create(contact))))
.setRegistrationExpirationTime(END_OF_TIME)
.build();
}
public static DomainApplication newDomainApplication(String domainName) {
// This ensures that the domain application gets the next available repoId before the created
// contact does, which is usually the applicationId 1.
return newDomainApplication(
domainName,
generateNewDomainRoid(getTldFromDomainName(domainName)),
persistActiveContact("contact1234"),
LaunchPhase.SUNRISE);
}
public static DomainApplication newDomainApplication(String domainName, ContactResource contact) {
return newDomainApplication(domainName, contact, LaunchPhase.SUNRISE);
}
public static DomainApplication newDomainApplication(
String domainName, ContactResource contact, LaunchPhase phase) {
return newDomainApplication(
domainName,
generateNewDomainRoid(getTldFromDomainName(domainName)),
contact,
phase);
}
public static DomainApplication newDomainApplication(
String domainName, String repoId, ContactResource contact, LaunchPhase phase) {
return new DomainApplication.Builder()
.setRepoId(repoId)
.setFullyQualifiedDomainName(domainName)
.setCurrentSponsorClientId("TheRegistrar")
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("2fooBAR")))
.setRegistrant(ReferenceUnion.create(contact))
.setContacts(ImmutableSet.of(
DesignatedContact.create(Type.ADMIN, ReferenceUnion.create(contact)),
DesignatedContact.create(Type.TECH, ReferenceUnion.create(contact))))
.setPhase(phase)
.setApplicationStatus(VALIDATED)
.addStatusValue(StatusValue.PENDING_CREATE)
.build();
}
public static DomainApplication newSunriseApplication(String domainName) {
return newSunriseApplication(domainName, persistActiveContact("contact1234"));
}
public static DomainApplication newSunriseApplication(
String domainName, ContactResource contact) {
return newDomainApplication(domainName, contact, LaunchPhase.SUNRISE)
.asBuilder()
.setEncodedSignedMarks(ImmutableList.of(EncodedSignedMark.create("base64", "abcdef")))
.build();
}
/**
* Returns a newly created {@link ContactResource} for the given contactId (which is the foreign
* key) with an auto-generated repoId.
*/
public static ContactResource newContactResource(String contactId) {
return newContactResourceWithRoid(contactId, generateNewContactHostRoid());
}
public static ContactResource newContactResourceWithRoid(String contactId, String repoId) {
return new ContactResource.Builder()
.setRepoId(repoId)
.setContactId(contactId)
.setCurrentSponsorClientId("TheRegistrar")
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("2fooBAR")))
.build();
}
public static Registry newRegistry(String tld, String roidSuffix) {
return newRegistry(
tld, roidSuffix, ImmutableSortedMap.of(START_OF_TIME, TldState.GENERAL_AVAILABILITY));
}
public static Registry newRegistry(
String tld, String roidSuffix, ImmutableSortedMap<DateTime, TldState> tldStates) {
return new Registry.Builder()
.setTldStr(tld)
.setRoidSuffix(roidSuffix)
.setTldStateTransitions(tldStates)
// Set billing costs to distinct small primes to avoid masking bugs in tests.
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 11)))
.setCreateBillingCost(Money.of(USD, 13))
.setRestoreBillingCost(Money.of(USD, 17))
.setServerStatusChangeBillingCost(Money.of(USD, 19))
// Always set a default premium list. Tests that don't want it can delete it.
.setPremiumList(persistPremiumList(tld, DEFAULT_PREMIUM_LIST_CONTENTS.get()))
.build();
}
public static ContactResource persistActiveContact(String contactId) {
return persistResource(newContactResource(contactId));
}
public static ContactResource persistDeletedContact(String contactId, DateTime now) {
return persistResource(
newContactResource(contactId)
.asBuilder()
.setDeletionTime(now.minusDays(1))
.build());
}
public static HostResource persistActiveHost(String hostName) {
return persistResource(newHostResource(hostName));
}
public static HostResource persistActiveSubordinateHost(
String hostName, DomainResource superordinateDomain) {
checkNotNull(superordinateDomain);
return persistResource(
newHostResource(hostName)
.asBuilder()
.setSuperordinateDomain(Ref.create(superordinateDomain))
.build());
}
public static HostResource persistDeletedHost(String hostName, DateTime now) {
return persistResource(
newHostResource(hostName).asBuilder().setDeletionTime(now.minusDays(1)).build());
}
public static DomainResource persistActiveDomain(String domainName) {
return persistResource(newDomainResource(domainName));
}
public static DomainApplication persistActiveDomainApplication(String domainName) {
return persistResource(newDomainApplication(domainName));
}
public static DomainApplication persistActiveDomainApplication(
String domainName, ContactResource contact, LaunchPhase phase) {
return persistResource(newDomainApplication(domainName, contact, phase));
}
public static DomainApplication persistDeletedDomainApplication(String domainName, DateTime now) {
return persistResource(newDomainApplication(domainName).asBuilder()
.setDeletionTime(now.minusDays(1)).build());
}
public static DomainResource persistDeletedDomain(String domainName, DateTime now) {
return persistDomainAsDeleted(newDomainResource(domainName), now);
}
public static DomainResource persistDomainAsDeleted(DomainResource domain, DateTime now) {
return persistResource(
domain.asBuilder().setDeletionTime(now.minusDays(1)).build());
}
/** Persists a domain and enqueues a LORDN task of the appropriate type for it. */
public static DomainResource persistDomainAndEnqueueLordn(final DomainResource domain) {
final DomainResource persistedDomain = persistResource(domain);
// Calls {@link LordnTask#enqueueDomainResourceTask} wrapped in an ofy transaction so that the
// transaction time is set correctly.
ofy().transactNew(new VoidWork() {
@Override
public void vrun() {
LordnTask.enqueueDomainResourceTask(persistedDomain);
}});
return persistedDomain;
}
public static ReservedList persistReservedList(String listName, String... lines) {
return persistReservedList(listName, true, lines);
}
public static ReservedList persistReservedList(
String listName, boolean shouldPublish, String... lines) {
return persistResource(
new ReservedList.Builder()
.setName(listName)
.setReservedListMapFromLines(ImmutableList.copyOf(lines))
.setShouldPublish(shouldPublish)
.build());
}
public static PremiumList persistPremiumList(String listName, String... lines) {
Optional<PremiumList> existing = PremiumList.get(listName);
return persistPremiumList(
(existing.isPresent() ? existing.get().asBuilder() : new PremiumList.Builder())
.setName(listName)
.setPremiumListMapFromLines(ImmutableList.copyOf(lines))
.build());
}
private static PremiumList persistPremiumList(PremiumList premiumList) {
// Persist the list and its child entities directly, rather than using its helper method, so
// that we can avoid writing commit logs. This would cause issues since many tests replace the
// clock in Ofy with a non-advancing FakeClock, and commit logs currently require
// monotonically increasing timestamps.
ofy().saveWithoutBackup().entity(premiumList).now();
ofy().saveWithoutBackup().entities(premiumList.getPremiumListEntries().values()).now();
return premiumList;
}
/** Creates and persists a tld. */
public static void createTld(String tld) {
createTld(tld, TldState.GENERAL_AVAILABILITY);
}
public static void createTld(String tld, String roidSuffix) {
createTld(tld, roidSuffix, ImmutableSortedMap.of(START_OF_TIME, TldState.GENERAL_AVAILABILITY));
}
/** Creates and persists the given TLDs. */
public static void createTlds(String... tlds) {
for (String tld : tlds) {
createTld(tld, TldState.GENERAL_AVAILABILITY);
}
}
public static void createTld(String tld, TldState tldState) {
createTld(tld, ImmutableSortedMap.of(START_OF_TIME, tldState));
}
public static void createTld(String tld, ImmutableSortedMap<DateTime, TldState> tldStates) {
createTld(tld, tld.replaceFirst(ACE_PREFIX_REGEX, "").toUpperCase(), tldStates);
}
public static void createTld(
String tld, String roidSuffix, ImmutableSortedMap<DateTime, TldState> tldStates) {
persistResource(newRegistry(tld, roidSuffix, tldStates));
allowRegistrarAccess("TheRegistrar", tld);
allowRegistrarAccess("NewRegistrar", tld);
}
public static void deleteTld(String tld) {
deleteResource(Registry.get(tld));
disallowRegistrarAccess("TheRegistrar", tld);
disallowRegistrarAccess("NewRegistrar", tld);
}
public static void allowRegistrarAccess(String clientId, String tld) {
Registrar registrar = Registrar.loadByClientId(clientId);
persistResource(
registrar.asBuilder().setAllowedTlds(union(registrar.getAllowedTlds(), tld)).build());
}
private static void disallowRegistrarAccess(String clientId, String tld) {
Registrar registrar = Registrar.loadByClientId(clientId);
persistResource(
registrar.asBuilder().setAllowedTlds(difference(registrar.getAllowedTlds(), tld)).build());
}
private static Builder createTransferDataBuilder(DateTime requestTime, DateTime expirationTime) {
return new TransferData.Builder()
.setTransferStatus(TransferStatus.PENDING)
.setGainingClientId("NewRegistrar")
.setTransferRequestTime(requestTime)
.setLosingClientId("TheRegistrar")
.setPendingTransferExpirationTime(expirationTime);
}
private static Builder createTransferDataBuilder(
DateTime requestTime, DateTime expirationTime, Integer extendedRegistrationYears) {
return createTransferDataBuilder(requestTime, expirationTime)
.setExtendedRegistrationYears(extendedRegistrationYears);
}
public static PollMessage.OneTime createPollMessageForImplicitTransfer(
EppResource contact,
HistoryEntry historyEntry,
String clientId,
DateTime requestTime,
DateTime expirationTime,
DateTime now) {
return new PollMessage.OneTime.Builder()
.setClientId(clientId)
.setEventTime(expirationTime)
.setMsg("Transfer server approved.")
.setResponseData(ImmutableList.of(
createTransferResponse(contact, createTransferDataBuilder(requestTime, expirationTime)
.build(),
now)))
.setParent(historyEntry)
.build();
}
public static BillingEvent.OneTime createBillingEventForTransfer(
DomainResource domain,
HistoryEntry historyEntry,
DateTime costLookupTime,
DateTime eventTime,
Integer extendedRegistrationYears) {
return new BillingEvent.OneTime.Builder()
.setReason(Reason.TRANSFER)
.setTargetId(domain.getFullyQualifiedDomainName())
.setEventTime(eventTime)
.setBillingTime(
eventTime.plus(Registry.get(domain.getTld()).getTransferGracePeriodLength()))
.setClientId("NewRegistrar")
.setPeriodYears(extendedRegistrationYears)
.setCost(Registry.get(domain.getTld()).getDomainRenewCost(
domain.getFullyQualifiedDomainName(),
extendedRegistrationYears,
costLookupTime))
.setParent(historyEntry)
.build();
}
public static ContactResource persistContactWithPendingTransfer(
ContactResource contact,
DateTime requestTime,
DateTime expirationTime,
DateTime now) {
HistoryEntry historyEntryContactTransfer = persistResource(
new HistoryEntry.Builder()
.setType(HistoryEntry.Type.CONTACT_TRANSFER_REQUEST)
.setParent(contact)
.build());
return persistResource(
contact.asBuilder()
.setCurrentSponsorClientId("TheRegistrar")
.addStatusValue(StatusValue.PENDING_TRANSFER)
.setTransferData(createTransferDataBuilder(requestTime, expirationTime)
.setPendingTransferExpirationTime(now.plus(
RegistryEnvironment.get().config().getContactAutomaticTransferLength()))
.setServerApproveEntities(
ImmutableSet.<Key<? extends TransferServerApproveEntity>>of(
// Pretend it's 3 days since the request
Key.create(persistResource(
createPollMessageForImplicitTransfer(
contact,
historyEntryContactTransfer,
"NewRegistrar",
requestTime,
expirationTime,
now))),
Key.create(persistResource(
createPollMessageForImplicitTransfer(
contact,
historyEntryContactTransfer,
"TheRegistrar",
requestTime,
expirationTime,
now)))))
.setTransferRequestTrid(Trid.create("transferClient-trid", "transferServer-trid"))
.build())
.build());
}
public static DomainResource persistDomainWithPendingTransfer(
DomainResource domain,
DateTime requestTime,
DateTime expirationTime,
DateTime extendedRegistrationExpirationTime,
int extendedRegistrationYears,
DateTime now) {
HistoryEntry historyEntryDomainTransfer = persistResource(
new HistoryEntry.Builder()
.setType(HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST)
.setParent(domain)
.build());
BillingEvent.OneTime transferBillingEvent = persistResource(createBillingEventForTransfer(
domain,
historyEntryDomainTransfer,
requestTime,
expirationTime,
extendedRegistrationYears));
BillingEvent.Recurring gainingClientAutorenewEvent = persistResource(
new BillingEvent.Recurring.Builder()
.setReason(Reason.AUTO_RENEW)
.setTargetId(domain.getFullyQualifiedDomainName())
.setClientId("NewRegistrar")
.setEventTime(extendedRegistrationExpirationTime)
.setRecurrenceEndTime(END_OF_TIME)
.setParent(historyEntryDomainTransfer)
.build());
PollMessage.Autorenew gainingClientAutorenewPollMessage = persistResource(
new PollMessage.Autorenew.Builder()
.setTargetId(domain.getFullyQualifiedDomainName())
.setClientId("NewRegistrar")
.setEventTime(extendedRegistrationExpirationTime)
.setAutorenewEndTime(END_OF_TIME)
.setMsg("Domain was auto-renewed.")
.setParent(historyEntryDomainTransfer)
.build());
// Modify the existing autorenew event to reflect the pending transfer.
persistResource(
domain.getAutorenewBillingEvent().get().asBuilder()
.setRecurrenceEndTime(expirationTime)
.build());
// Update the end time of the existing autorenew poll message. We must delete it if it has no
// events left in it.
if (domain.getAutorenewPollMessage().get().getEventTime().isBefore(expirationTime)) {
persistResource(
domain.getAutorenewPollMessage().get().asBuilder()
.setAutorenewEndTime(expirationTime)
.build());
} else {
deleteResource(domain.getAutorenewPollMessage().get());
}
Builder transferDataBuilder = createTransferDataBuilder(
requestTime, expirationTime, extendedRegistrationYears);
return persistResource(domain.asBuilder()
.setCurrentSponsorClientId("TheRegistrar")
.addStatusValue(StatusValue.PENDING_TRANSFER)
.setTransferData(transferDataBuilder
.setPendingTransferExpirationTime(expirationTime)
.setServerApproveBillingEvent(Ref.create(transferBillingEvent))
.setServerApproveAutorenewEvent(Ref.create(gainingClientAutorenewEvent))
.setServerApproveAutorenewPollMessage(Ref.create(gainingClientAutorenewPollMessage))
.setServerApproveEntities(ImmutableSet.<Key<? extends TransferServerApproveEntity>>of(
Key.create(transferBillingEvent),
Key.create(gainingClientAutorenewEvent),
Key.create(gainingClientAutorenewPollMessage),
Key.create(persistResource(
createPollMessageForImplicitTransfer(
domain,
historyEntryDomainTransfer,
"NewRegistrar",
requestTime,
expirationTime,
now))),
Key.create(persistResource(
createPollMessageForImplicitTransfer(
domain,
historyEntryDomainTransfer,
"TheRegistrar",
requestTime,
expirationTime,
now)))))
.setTransferRequestTrid(Trid.create("transferClient-trid", "transferServer-trid"))
.setExtendedRegistrationYears(extendedRegistrationYears)
.build())
.build());
}
private static Iterable<BillingEvent> getBillingEvents() {
return Iterables.<BillingEvent>concat(
ofy().load().type(BillingEvent.OneTime.class),
ofy().load().type(BillingEvent.Recurring.class),
ofy().load().type(BillingEvent.Cancellation.class));
}
private static Iterable<BillingEvent> getBillingEvents(EppResource resource) {
return Iterables.<BillingEvent>concat(
ofy().load().type(BillingEvent.OneTime.class).ancestor(resource),
ofy().load().type(BillingEvent.Recurring.class).ancestor(resource),
ofy().load().type(BillingEvent.Cancellation.class).ancestor(resource));
}
/** Assert that the expected billing events are exactly the ones found in the fake datastore. */
public static void assertBillingEvents(BillingEvent... expected) throws Exception {
assertThat(FluentIterable.from(getBillingEvents()).transform(BILLING_EVENT_ID_STRIPPER))
.containsExactlyElementsIn(
FluentIterable.from(asList(expected)).transform(BILLING_EVENT_ID_STRIPPER));
}
/**
* Assert that the expected billing events are exactly the ones found for the given EppResource.
*/
public static void assertBillingEventsForResource(
EppResource resource, BillingEvent... expected) throws Exception {
assertThat(FluentIterable.from(getBillingEvents(resource)).transform(BILLING_EVENT_ID_STRIPPER))
.containsExactlyElementsIn(
FluentIterable.from(asList(expected)).transform(BILLING_EVENT_ID_STRIPPER));
}
/** Assert that there are no billing events. */
public static void assertNoBillingEvents() {
assertThat(getBillingEvents()).isEmpty();
}
/** Helper to effectively erase the billing event ID to facilitate comparison. */
public static final Function<BillingEvent, BillingEvent> BILLING_EVENT_ID_STRIPPER =
new Function<BillingEvent, BillingEvent>() {
@Override
public BillingEvent apply(BillingEvent billingEvent) {
// Can't use id=0 because that causes the builder to generate a new id.
return billingEvent.asBuilder().setId(1L).build();
}};
public static ImmutableList<PollMessage> getPollMessages() {
return FluentIterable.from(ofy().load().type(PollMessage.class)).toList();
}
public static ImmutableList<PollMessage> getPollMessages(String clientId) {
return FluentIterable
.from(ofy().load().type(PollMessage.class).filter("clientId", clientId))
.toList();
}
public static ImmutableList<PollMessage> getPollMessages(String clientId, DateTime now) {
return FluentIterable
.from(ofy()
.load()
.type(PollMessage.class)
.filter("clientId", clientId)
.filter("eventTime <=", now.toDate()))
.toList();
}
/** Gets all PollMessages associated with the given EppResource. */
public static ImmutableList<PollMessage> getPollMessages(
EppResource resource, String clientId, DateTime now) {
return FluentIterable
.from(ofy()
.load()
.type(PollMessage.class)
.ancestor(resource)
.filter("clientId", clientId)
.filter("eventTime <=", now.toDate()))
.toList();
}
public static PollMessage getOnlyPollMessage(String clientId) {
return Iterables.getOnlyElement(getPollMessages(clientId));
}
public static PollMessage getOnlyPollMessage(String clientId, DateTime now) {
return Iterables.getOnlyElement(getPollMessages(clientId, now));
}
public static PollMessage getOnlyPollMessage(
String clientId,
DateTime now,
Class<? extends PollMessage> subType) {
return Iterables.getOnlyElement(Iterables.filter(getPollMessages(clientId, now), subType));
}
public static PollMessage getOnlyPollMessage(
EppResource resource,
String clientId,
DateTime now,
Class<? extends PollMessage> subType) {
return Iterables.getOnlyElement(
Iterables.filter(getPollMessages(resource, clientId, now), subType));
}
/** Returns a newly allocated, globally unique domain repoId of the format HEX-TLD. */
public static String generateNewDomainRoid(String tld) {
return createDomainRoid(ObjectifyService.allocateId(), tld);
}
/**
* Returns a newly allocated, globally unique contact/host repoId of the format
* HEX_TLD-ROID.
*/
public static String generateNewContactHostRoid() {
return createContactHostRoid(ObjectifyService.allocateId());
}
/**
* Persists a test resource to Datastore and returns it.
*
* <p>Tests should always use this method (or the shortcut persist methods in this class) to
* persist test data, to avoid potentially subtle bugs related to race conditions and a stale
* ofy() session cache. Specifically, this method calls .now() on the save to force the write to
* actually get sent to datastore (although it does not force it to be applied) and clears the
* session cache. If necessary, this method also updates the relevant {@link EppResourceIndex},
* {@link ForeignKeyIndex} and {@link DomainApplicationIndex}.
*
* <p><b>Note:</b> Your resource will not be enrolled in a commit log. If you want backups, use
* {@link #persistResourceWithCommitLog(Object)}.
*/
public static <R> R persistResource(final R resource) {
return persistResource(resource, false);
}
/** Same as {@link #persistResource(Object)} with backups enabled. */
public static <R> R persistResourceWithCommitLog(final R resource) {
return persistResource(resource, true);
}
private static <R> R persistResource(final R resource, final boolean wantBackup) {
ofy().transact(new VoidWork() {
@Override
public void vrun() {
Saver saver = wantBackup ? ofy().save() : ofy().saveWithoutBackup();
saver.entity(resource);
if (resource instanceof EppResource) {
EppResource eppResource = (EppResource) resource;
assertWithMessage("Cannot persist an EppResource with a missing repoId in tests")
.that(eppResource.getRepoId()).isNotEmpty();
Key<EppResource> eppResourceKey = Key.create(eppResource);
saver.entity(EppResourceIndex.create(eppResourceKey));
if (resource instanceof ForeignKeyedEppResource) {
saver.entity(ForeignKeyIndex.create(eppResource, eppResource.getDeletionTime()));
}
if (resource instanceof DomainApplication) {
saver.entity(
DomainApplicationIndex.createUpdatedInstance((DomainApplication) resource));
}
}
}});
// Force the session to be cleared so that when we read it back, we read from the datastore
// and not from the transaction cache or memcache.
ofy().clearSessionCache();
return ofy().load().entity(resource).now();
}
/**
* Saves an {@link EppResource} with partial history and commit log entries.
*
* <p>This was coded for testing RDE since its queries depend on the associated entries.
*
* <p><b>Warning:</b> If you call this multiple times in a single test, you need to inject Ofy's
* clock field and forward it by a millisecond between each subsequent call.
*
* @see #persistResource(Object)
*/
public static <R extends EppResource> R persistEppResource(final R resource) {
checkState(!ofy().inTransaction());
ofy().transact(new VoidWork() {
@Override
public void vrun() {
ofy().save().<ImmutableObject>entities(
resource,
new HistoryEntry.Builder()
.setParent(resource)
.setType(getHistoryEntryType(resource))
.setModificationTime(ofy().getTransactionTime())
.build());
ofy().save().entity(ForeignKeyIndex.create(resource, resource.getDeletionTime()));
}});
ofy().clearSessionCache();
return ofy().load().entity(resource).safe();
}
/** Returns all of the history entries that are parented off the given EppResource. */
public static List<HistoryEntry> getHistoryEntries(EppResource resource) {
return ofy().load()
.type(HistoryEntry.class)
.ancestor(resource)
.order("modificationTime")
.list();
}
/**
* Returns all of the history entries that are parented off the given EppResource with the given
* type.
*/
public static List<HistoryEntry> getHistoryEntriesOfType(
EppResource resource, final HistoryEntry.Type type) {
return FluentIterable.from(getHistoryEntries(resource))
.filter(new Predicate<HistoryEntry>() {
@Override
public boolean apply(HistoryEntry entry) {
return entry.getType() == type;
}})
.toList();
}
/**
* Returns the only history entry of the given type, and throws an AssertionError if there are
* zero or more than one.
*/
public static HistoryEntry getOnlyHistoryEntryOfType(
EppResource resource, final HistoryEntry.Type type) {
List<HistoryEntry> historyEntries = getHistoryEntriesOfType(resource, type);
assertThat(historyEntries).hasSize(1);
return historyEntries.get(0);
}
private static HistoryEntry.Type getHistoryEntryType(EppResource resource) {
if (resource instanceof ContactResource) {
return resource.getRepoId() != null
? HistoryEntry.Type.CONTACT_CREATE : HistoryEntry.Type.CONTACT_UPDATE;
} else if (resource instanceof HostResource) {
return resource.getRepoId() != null
? HistoryEntry.Type.HOST_CREATE : HistoryEntry.Type.HOST_UPDATE;
} else if (resource instanceof DomainResource) {
return resource.getRepoId() != null
? HistoryEntry.Type.DOMAIN_CREATE : HistoryEntry.Type.DOMAIN_UPDATE;
} else {
throw new AssertionError();
}
}
public static PollMessage getOnlyPollMessageForHistoryEntry(HistoryEntry historyEntry) {
return Iterables.getOnlyElement(ofy().load()
.type(PollMessage.class)
.ancestor(historyEntry));
}
public static <T extends EppResource> HistoryEntry createHistoryEntryForEppResource(
T parentResource) {
return persistResource(new HistoryEntry.Builder().setParent(parentResource).build());
}
/** Persists a single Objectify resource, without adjusting foreign resources or keys. */
public static <R> R persistSimpleResource(final R resource) {
return persistSimpleResources(ImmutableList.of(resource)).get(0);
}
/**
* Persists a single Objectify resource in the global namespace, without adjusting foreign
* resources or keys.
*/
public static <R> R persistSimpleGlobalResource(final R resource) {
return persistSimpleResources(ImmutableList.of(resource)).get(0);
}
/**
* Like persistResource but for multiple entities, with no helper for saving
* ForeignKeyedEppResources. All entities are persisted into the global namespace.
*
* @see "http://docs.objectify-appengine.googlecode.com/git/apidocs/com/googlecode/objectify/cmd/Loader.htmls#entities(java.lang.Iterable)"
*/
public static <R> ImmutableList<R> persistSimpleGlobalResources(Iterable<R> resources) {
return persistSimpleResources(resources);
}
/**
* Like persistResource but for multiple entities, with no helper for saving
* ForeignKeyedEppResources.
*
* @see "http://docs.objectify-appengine.googlecode.com/git/apidocs/com/googlecode/objectify/cmd/Loader.htmls#entities(java.lang.Iterable)"
*/
public static <R> ImmutableList<R> persistSimpleResources(final Iterable<R> resources) {
ofy().transact(new VoidWork(){
@Override
public void vrun() {
ofy().saveWithoutBackup().entities(resources);
}});
// Force the session to be cleared so that when we read it back, we read from the datastore
// and not from the transaction cache or memcache.
ofy().clearSessionCache();
return ImmutableList.copyOf(ofy().load().entities(resources).values());
}
public static void deleteResource(final Object resource) {
ofy().deleteWithoutBackup().entity(resource).now();
// Force the session to be cleared so that when we read it back, we read from the datastore and
// not from the transaction cache or memcache.
ofy().clearSessionCache();
}
/** Force the create and update timestamps to get written into the resource. **/
public static <R> R cloneAndSetAutoTimestamps(final R resource) {
return ofy().transact(new Work<R>() {
@Override
public R run() {
return ofy().load().fromEntity(ofy().save().toEntity(resource));
}});
}
private DatastoreHelper() {}
}

View file

@ -0,0 +1,84 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableSet;
import com.google.common.truth.AbstractVerb.DelegatedVerb;
import com.google.common.truth.FailureStrategy;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.domain.launch.ApplicationStatus;
import com.google.domain.registry.model.smd.EncodedSignedMark;
import com.google.domain.registry.testing.TruthChainer.And;
import java.util.Objects;
/** Truth subject for asserting things about {@link DomainApplication} instances. */
public final class DomainApplicationSubject
extends AbstractDomainBaseSubject<DomainApplication, DomainApplicationSubject> {
public And<DomainApplicationSubject> hasApplicationStatus(
ApplicationStatus applicationStatus) {
if (!Objects.equals(getSubject().getApplicationStatus(), applicationStatus)) {
failWithBadResults(
"has application status", applicationStatus, getSubject().getApplicationStatus());
}
return andChainer();
}
public And<DomainApplicationSubject> doesNotHaveApplicationStatus(
ApplicationStatus applicationStatus) {
return doesNotHaveValue(
applicationStatus,
getSubject().getApplicationStatus(),
"application status");
}
public And<DomainApplicationSubject> hasExactlyEncodedSignedMarks(
EncodedSignedMark... encodedSignedMarks) {
if (!Objects.equals(
ImmutableSet.copyOf(getSubject().getEncodedSignedMarks()),
ImmutableSet.of(encodedSignedMarks))) {
assertThat(getSubject().getEncodedSignedMarks())
.named("the encoded signed marks of " + getDisplaySubject())
.containsExactly((Object[]) encodedSignedMarks);
}
return andChainer();
}
public And<DomainApplicationSubject> hasNumEncodedSignedMarks(int num) {
if (getSubject().getEncodedSignedMarks().size() != num) {
failWithBadResults(
"has this many encoded signed marks: ", num, getSubject().getEncodedSignedMarks().size());
}
return andChainer();
}
/** A factory for instances of this subject. */
private static class SubjectFactory
extends ReflectiveSubjectFactory<DomainApplication, DomainApplicationSubject>{}
public DomainApplicationSubject(FailureStrategy strategy, DomainApplication subject) {
super(strategy, checkNotNull(subject));
}
public static DelegatedVerb<DomainApplicationSubject, DomainApplication>
assertAboutApplications() {
return assertAbout(new SubjectFactory());
}
}

View file

@ -0,0 +1,72 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.AbstractVerb.DelegatedVerb;
import com.google.common.truth.FailureStrategy;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.testing.TruthChainer.And;
import org.joda.time.DateTime;
import java.util.Objects;
/** Truth subject for asserting things about {@link DomainResource} instances. */
public final class DomainResourceSubject
extends AbstractDomainBaseSubject<DomainResource, DomainResourceSubject> {
/** A factory for instances of this subject. */
private static class SubjectFactory
extends ReflectiveSubjectFactory<DomainResource, DomainResourceSubject>{}
public And<DomainResourceSubject> hasRegistrationExpirationTime(DateTime expiration) {
if (!Objects.equals(getSubject().getRegistrationExpirationTime(), expiration)) {
failWithBadResults(
"has registrationExpirationTime",
expiration,
getSubject().getRegistrationExpirationTime());
}
return andChainer();
}
public And<DomainResourceSubject> hasDeletePollMessage() {
if (getSubject().getDeletePollMessage() == null) {
fail("has a delete poll message");
}
return andChainer();
}
public And<DomainResourceSubject> hasNoDeletePollMessage() {
if (getSubject().getDeletePollMessage() != null) {
fail("has no delete poll message");
}
return andChainer();
}
public And<DomainResourceSubject> hasSmdId(String smdId) {
return hasValue(smdId, getSubject().getSmdId(), "has smdId");
}
public DomainResourceSubject(FailureStrategy strategy, DomainResource subject) {
super(strategy, checkNotNull(subject));
}
public static DelegatedVerb<DomainResourceSubject, DomainResource> assertAboutDomains() {
return assertAbout(new SubjectFactory());
}
}

View file

@ -0,0 +1,50 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.domain.registry.testing.TestDataHelper.loadFileWithSubstitutions;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.flows.EppXmlTransformer;
import com.google.domain.registry.model.eppinput.EppInput;
import java.util.Map;
/** Test rule that loads an Epp object from a file. */
public class EppLoader {
private String eppXml;
public EppLoader(Object context, String eppXmlFilename) {
this(context, eppXmlFilename, ImmutableMap.<String, String>of());
}
public EppLoader(Object context, String eppXmlFilename, Map<String, String> substitutions) {
this.eppXml = loadFileWithSubstitutions(context.getClass(), eppXmlFilename, substitutions);
}
public EppInput getEpp() throws Exception {
return EppXmlTransformer.unmarshal(eppXml.getBytes(UTF_8));
}
public String getEppXml() {
return eppXml;
}
public void replaceAll(String regex, String substitution) {
eppXml = eppXml.replaceAll(regex, substitution);
}
}

View file

@ -0,0 +1,107 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.base.Throwables.getRootCause;
import static com.google.domain.registry.flows.EppXmlTransformer.marshal;
import com.google.domain.registry.flows.EppException;
import com.google.domain.registry.model.eppcommon.Trid;
import com.google.domain.registry.model.eppoutput.EppOutput;
import com.google.domain.registry.model.eppoutput.Response;
import com.google.domain.registry.util.Clock;
import com.google.domain.registry.util.SystemClock;
import com.google.domain.registry.xml.ValidationMode;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import javax.annotation.Nullable;
/**
* A test rule similar to JUnit's {@code ExpectedException} rule that does extra checking to ensure
* that {@link EppException} derivatives have EPP-compliant error messages.
*/
public class ExceptionRule implements TestRule {
private static final Clock CLOCK = new SystemClock();
@Nullable
Class<? extends Throwable> expectedExceptionClass;
@Nullable
String expectedMessage;
private boolean useRootCause;
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
base.evaluate();
if (expectedExceptionClass != null) {
throw new AssertionError(String.format(
"Expected test to throw %s%s",
expectedExceptionClass.getSimpleName(),
expectedMessage == null ? "" : (" with message: " + expectedMessage)));
}
} catch (Throwable e) {
Throwable cause = useRootCause ? getRootCause(e) : e;
if (expectedExceptionClass == null
|| !(expectedExceptionClass.isAssignableFrom(cause.getClass())
&& nullToEmpty(cause.getMessage()).contains(nullToEmpty(expectedMessage)))) {
throw e; // We didn't expect this so pass it through.
}
if (e instanceof EppException) {
// Attempt to marshall the exception to EPP. If it doesn't work, this will throw.
marshal(
EppOutput.create(new Response.Builder()
.setTrid(Trid.create(null))
.setResult(((EppException) e).getResult())
.setExecutionTime(CLOCK.nowUtc())
.build()),
ValidationMode.STRICT);
}
}
}};
}
public void expect(Class<? extends Throwable> expectedExceptionClass) {
checkState(this.expectedExceptionClass == null,
"Don't use multiple `thrown.expect()` statements in your test.");
this.expectedExceptionClass = expectedExceptionClass;
}
public void expect(Class<? extends Throwable> expectedExceptionClass, String expectedMessage) {
expect(expectedExceptionClass);
this.expectedMessage = expectedMessage;
}
public void expectRootCause(Class<? extends Throwable> expectedExceptionClass) {
expect(expectedExceptionClass);
this.useRootCause = true;
}
public void expectRootCause(
Class<? extends Throwable> expectedExceptionClass, String expectedMessage) {
expect(expectedExceptionClass, expectedMessage);
this.useRootCause = true;
}
}

View file

@ -0,0 +1,55 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* Display helpful failure message if a mocked method is called.
*
* <p>One important problem this solves is when you mock servlets and the test fails, you usually
* end up with failure messages like {@code Wanted but not invoked: rsp.setStatus(200)} which
* aren't very helpful. This is because servlets normally report problems by calling
* {@link javax.servlet.http.HttpServletResponse#sendError(int, String) rsp.sendError()} so it'd be
* nice if we could have the error message be whatever arguments get passed to {@code sendError}.
*
* <p>And that's where {@link FailAnswer} comes to the rescue! Here's an example of what you could
* put at the beginning of a servlet test method to have better error messages:
*
* <pre> {@code
* doAnswer(new FailAnswer<>()).when(rsp).sendError(anyInt());
* doAnswer(new FailAnswer<>()).when(rsp).sendError(anyInt(), anyString());
* }</pre>
*
* @param <T> The return type of the mocked method (which doesn't actually return).
*/
public class FailAnswer<T> implements Answer<T> {
@Override
public T answer(@SuppressWarnings("null") InvocationOnMock args) throws Throwable {
StringBuilder msg = new StringBuilder();
boolean first = true;
for (Object arg : args.getArguments()) {
if (first) {
first = false;
} else {
msg.append(", ");
}
msg.append(arg);
}
throw new AssertionError(msg.toString());
}
}

View file

@ -0,0 +1,69 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.domain.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import static org.joda.time.Duration.millis;
import com.google.domain.registry.util.Clock;
import org.joda.time.DateTime;
import org.joda.time.ReadableDuration;
import org.joda.time.ReadableInstant;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.concurrent.ThreadSafe;
/** A mock clock for testing purposes that supports telling, setting, and advancing the time. */
@ThreadSafe
public final class FakeClock implements Clock {
// Clock isn't a thread synchronization primitive, but tests involving
// threads should see a consistent flow.
private final AtomicLong currentTimeMillis = new AtomicLong();
/** Creates a FakeClock that starts at START_OF_TIME. */
public FakeClock() {
this(START_OF_TIME);
}
/** Creates a FakeClock initialized to a specific time. */
public FakeClock(ReadableInstant startTime) {
setTo(startTime);
}
/** Returns the current time. */
@Override
public DateTime nowUtc() {
return new DateTime(currentTimeMillis.get(), UTC);
}
/** Advances clock by one millisecond. */
public void advanceOneMilli() {
advanceBy(millis(1));
}
/** Advances clock by some duration. */
public void advanceBy(ReadableDuration duration) {
currentTimeMillis.addAndGet(duration.getMillis());
}
/** Sets the time to the specified instant. */
public void setTo(ReadableInstant time) {
currentTimeMillis.set(time.getMillis());
}
}

View file

@ -0,0 +1,43 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import com.google.domain.registry.request.JsonResponse;
import java.util.Map;
/** Fake implementation of {@link JsonResponse} for testing. */
public final class FakeJsonResponse extends JsonResponse {
private Map<String, ?> responseMap;
public FakeJsonResponse() {
super(new FakeResponse());
}
@Override
public void setPayload(Map<String, ?> responseMap) {
this.responseMap = responseMap;
super.setPayload(responseMap);
}
public Map<String, ?> getResponseMap() {
return responseMap;
}
public int getStatus() {
return ((FakeResponse) response).getStatus();
}
}

View file

@ -0,0 +1,105 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Collections.unmodifiableMap;
import com.google.common.base.Throwables;
import com.google.common.net.MediaType;
import com.google.domain.registry.request.Response;
import org.joda.time.DateTime;
import java.util.HashMap;
import java.util.Map;
/** Fake implementation of {@link Response} for testing. */
public final class FakeResponse implements Response {
private int status = 200;
private MediaType contentType = MediaType.HTML_UTF_8;
private String payload = "";
private final Map<String, Object> headers = new HashMap<>();
private boolean wasMutuallyExclusiveResponseSet;
private String lastResponseStackTrace;
public int getStatus() {
return status;
}
public MediaType getContentType() {
return contentType;
}
public String getPayload() {
return payload;
}
public Map<String, Object> getHeaders() {
return unmodifiableMap(headers);
}
@Override
public void setStatus(int status) {
checkArgument(status >= 100);
this.status = status;
}
@Override
public void setContentType(MediaType contentType) {
this.contentType = checkNotNull(contentType);
}
@Override
public void setPayload(String payload) {
checkResponsePerformedOnce();
this.payload = checkNotNull(payload);
}
@Override
public void setHeader(String header, String value) {
headers.put(checkNotNull(header), checkNotNull(value));
}
@Override
public void setDateHeader(String header, DateTime timestamp) {
headers.put(checkNotNull(header), checkNotNull(timestamp));
}
@Override
public void sendJavaScriptRedirect(String redirectUrl) {
checkResponsePerformedOnce();
this.status = 200;
this.payload = "Javascript redirect to " + redirectUrl;
}
private void checkResponsePerformedOnce() {
checkState(!wasMutuallyExclusiveResponseSet,
"Two responses were sent. Here's the previous call:\n%s", lastResponseStackTrace);
wasMutuallyExclusiveResponseSet = true;
lastResponseStackTrace = getStackTrace();
}
private static String getStackTrace() {
try {
throw new Exception();
} catch (Exception e) {
return Throwables.getStackTraceAsString(e);
}
}
}

View file

@ -0,0 +1,58 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import com.google.common.io.ByteSource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ServletInputStream;
/**
* Used to mock the return value of {@link javax.servlet.ServletRequest#getInputStream}.
*
* <p>Most servlets will call {@link javax.servlet.ServletRequest#getReader}, in which case you
* can simply return a {@link java.io.StringReader} instance. But the getInputStream method is
* not as simple to mock and requires an implementing class.
*/
public final class FakeServletInputStream extends ServletInputStream {
private final InputStream input;
public FakeServletInputStream(byte[] buf) {
this.input = new ByteArrayInputStream(buf);
}
/**
* Use a {@link ByteSource} as input for the servlet. Be sure to call {@link #close} after
* your servlet runs so the resource opened via {@code bytes} gets closed.
* @throws IOException
*/
public FakeServletInputStream(ByteSource bytes) throws IOException {
this.input = bytes.openStream();
}
@Override
public int read() throws IOException {
return input.read();
}
@Override
public void close() throws IOException {
input.close();
}
}

View file

@ -0,0 +1,65 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.annotation.Nonnull;
import javax.servlet.ServletOutputStream;
/**
* Used to mock the return value of {@link javax.servlet.ServletResponse#getOutputStream}.
*
* <p>Most servlets will call {@link javax.servlet.ServletResponse#getWriter}, in which case you
* can simply return a {@link java.io.StringWriter} instance. But the getOutputStream method is
* not as simple to mock and requires an implementing class.
*/
public final class FakeServletOutputStream extends ServletOutputStream {
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
/** @see java.io.OutputStream#write(int) */
@Override
public void write(int b) throws IOException {
out.write(b);
}
/** @see java.io.OutputStream#write(byte[]) */
@Override
public void write(@Nonnull @SuppressWarnings("null") byte[] b) throws IOException {
out.write(b);
}
/** @see java.io.OutputStream#write(byte[], int, int) */
@Override
public void write(@Nonnull @SuppressWarnings("null") byte[] b, int off, int len)
throws IOException {
out.write(b, off, len);
}
/** Converts contents to a string, assuming UTF-8 encoding. */
@Override
public String toString() {
try {
return out.toString(UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,50 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.domain.registry.util.Sleeper;
import org.joda.time.ReadableDuration;
import javax.annotation.concurrent.ThreadSafe;
/** Sleeper implementation for unit tests that advances {@link FakeClock} rather than sleep. */
@ThreadSafe
public final class FakeSleeper implements Sleeper {
private final FakeClock clock;
public FakeSleeper(FakeClock clock) {
this.clock = checkNotNull(clock, "clock");
}
@Override
public void sleep(ReadableDuration duration) throws InterruptedException {
checkArgument(duration.getMillis() >= 0);
if (Thread.interrupted()) {
throw new InterruptedException();
}
clock.advanceBy(duration);
}
@Override
public void sleepUninterruptibly(ReadableDuration duration) {
checkArgument(duration.getMillis() >= 0);
clock.advanceBy(duration);
}
}

View file

@ -0,0 +1,50 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
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.common.collect.ImmutableList;
import java.io.IOException;
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) throws IOException {
URL requestURL = request.getURL();
if (backingMap.containsKey(requestURL)) {
return backingMap.get(requestURL);
} else {
return new HTTPResponse(
HttpURLConnection.HTTP_NOT_FOUND, null, null, ImmutableList.<HTTPHeader>of());
}
}
}

View file

@ -0,0 +1,50 @@
// Copyright 2016 Google Inc. 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 com.google.domain.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);
}
}
}

View file

@ -0,0 +1,219 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.domain.registry.model.domain.DomainUtils.getTldFromDomainName;
import static com.google.domain.registry.testing.DatastoreHelper.generateNewContactHostRoid;
import static com.google.domain.registry.testing.DatastoreHelper.generateNewDomainRoid;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
import com.google.domain.registry.model.contact.ContactAddress;
import com.google.domain.registry.model.contact.ContactPhoneNumber;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.contact.PostalInfo;
import com.google.domain.registry.model.domain.DesignatedContact;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.model.domain.ReferenceUnion;
import com.google.domain.registry.model.domain.secdns.DelegationSignerData;
import com.google.domain.registry.model.eppcommon.StatusValue;
import com.google.domain.registry.model.host.HostResource;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registrar.RegistrarAddress;
import com.google.domain.registry.model.registrar.RegistrarContact;
import com.google.domain.registry.util.Idn;
import org.joda.time.DateTime;
import java.net.InetAddress;
import java.util.List;
import javax.annotation.Nullable;
/** Test helper methods for the rdap and whois packages. */
public final class FullFieldsTestEntityHelper {
public static Registrar makeRegistrar(
String clientId, String registrarName, Registrar.State state) {
Registrar registrar = new Registrar.Builder()
.setClientIdentifier(clientId)
.setRegistrarName(registrarName)
.setType(Registrar.Type.REAL)
.setIanaIdentifier(1L)
.setState(state)
.setInternationalizedAddress(new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("123 Example Boulevard <script>"))
.setCity("Williamsburg <script>")
.setState("NY")
.setZip("11211")
.setCountryCode("US")
.build())
.setLocalizedAddress(new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("123 Example Boulevard <script>"))
.setCity("Williamsburg <script>")
.setState("NY")
.setZip("11211")
.setCountryCode("US")
.build())
.setPhoneNumber("+1.2125551212")
.setFaxNumber("+1.2125551213")
.setEmailAddress("contact-us@example.com")
.setWhoisServer("whois.example.com")
.setReferralUrl("http://www.example.com")
.build();
return registrar;
}
public static ImmutableList<RegistrarContact> makeRegistrarContacts(Registrar registrar) {
return ImmutableList.of(
new RegistrarContact.Builder()
.setParent(registrar)
.setName("John Doe")
.setEmailAddress("johndoe@example.com")
.setPhoneNumber("+1.2125551213")
.setFaxNumber("+1.2125551213")
.setTypes(ImmutableSet.of(RegistrarContact.Type.ADMIN))
// Purposely flip the internal/external admin/tech
// distinction to make sure we're not relying on it. Sigh.
.setVisibleInWhoisAsAdmin(false)
.setVisibleInWhoisAsTech(true)
.build(),
new RegistrarContact.Builder()
.setParent(registrar)
.setName("Jane Doe")
.setEmailAddress("janedoe@example.com")
.setPhoneNumber("+1.2125551215")
.setFaxNumber("+1.2125551216")
.setTypes(ImmutableSet.of(RegistrarContact.Type.TECH))
// Purposely flip the internal/external admin/tech
// distinction to make sure we're not relying on it. Sigh.
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.build());
}
public static HostResource makeHostResource(String fqhn, String ip) {
return makeHostResource(fqhn, ip, null);
}
public static HostResource makeHostResource(
String fqhn, @Nullable String ip1, @Nullable String ip2) {
HostResource.Builder builder = new HostResource.Builder()
.setRepoId(generateNewContactHostRoid())
.setFullyQualifiedHostName(Idn.toASCII(fqhn))
.setCreationTimeForTest(DateTime.parse("2000-10-08T00:45:00Z"));
if ((ip1 != null) || (ip2 != null)) {
ImmutableSet.Builder<InetAddress> ipBuilder = new ImmutableSet.Builder<>();
if (ip1 != null) {
ipBuilder.add(InetAddresses.forString(ip1));
}
if (ip2 != null) {
ipBuilder.add(InetAddresses.forString(ip2));
}
builder.setInetAddresses(ipBuilder.build());
}
return builder.build();
}
public static ContactResource makeContactResource(
String id, String name, @Nullable String email) {
return makeContactResource(
id, name, email, ImmutableList.of("123 Example Boulevard <script>"));
}
public static ContactResource makeContactResource(
String id, String name, @Nullable String email, @Nullable List<String> street) {
PostalInfo.Builder postalBuilder = new PostalInfo.Builder()
.setType(PostalInfo.Type.INTERNATIONALIZED)
.setName(name)
.setOrg("GOOGLE INCORPORATED <script>");
if (street != null) {
postalBuilder.setAddress(new ContactAddress.Builder()
.setStreet(ImmutableList.copyOf(street))
.setCity("KOKOMO")
.setState("BM")
.setZip("31337")
.setCountryCode("US")
.build());
}
ContactResource.Builder builder = new ContactResource.Builder()
.setContactId(id)
.setRepoId(generateNewContactHostRoid())
.setCreationTimeForTest(DateTime.parse("2000-10-08T00:45:00Z"))
.setInternationalizedPostalInfo(postalBuilder.build())
.setVoiceNumber(
new ContactPhoneNumber.Builder()
.setPhoneNumber("+1.2126660420")
.build())
.setFaxNumber(
new ContactPhoneNumber.Builder()
.setPhoneNumber("+1.2126660420")
.build());
if (email != null) {
builder.setEmailAddress(email);
}
return builder.build();
}
public static DomainResource makeDomainResource(
String domain,
@Nullable ContactResource registrant,
@Nullable ContactResource admin,
@Nullable ContactResource tech,
@Nullable HostResource ns1,
@Nullable HostResource ns2,
Registrar registrar) {
DomainResource.Builder builder = new DomainResource.Builder()
.setFullyQualifiedDomainName(Idn.toASCII(domain))
.setRepoId(generateNewDomainRoid(getTldFromDomainName(Idn.toASCII(domain))))
.setLastEppUpdateTime(DateTime.parse("2009-05-29T20:13:00Z"))
.setCreationTimeForTest(DateTime.parse("2000-10-08T00:45:00Z"))
.setRegistrationExpirationTime(DateTime.parse("2110-10-08T00:44:59Z"))
.setCurrentSponsorClientId(registrar.getClientIdentifier())
.setStatusValues(ImmutableSet.of(
StatusValue.CLIENT_DELETE_PROHIBITED,
StatusValue.CLIENT_RENEW_PROHIBITED,
StatusValue.CLIENT_TRANSFER_PROHIBITED,
StatusValue.SERVER_UPDATE_PROHIBITED))
.setDsData(ImmutableSet.of(new DelegationSignerData()));
if (registrant != null) {
builder.setRegistrant(ReferenceUnion.create(registrant));
}
if ((admin != null) || (tech != null)) {
ImmutableSet.Builder<DesignatedContact> contactsBuilder = new ImmutableSet.Builder<>();
if (admin != null) {
contactsBuilder.add(DesignatedContact.create(
DesignatedContact.Type.ADMIN, ReferenceUnion.create(admin)));
}
if (tech != null) {
contactsBuilder.add(DesignatedContact.create(
DesignatedContact.Type.TECH, ReferenceUnion.create(tech)));
}
builder.setContacts(contactsBuilder.build());
}
if ((ns1 != null) || (ns2 != null)) {
ImmutableSet.Builder<ReferenceUnion<HostResource>> nsBuilder = new ImmutableSet.Builder<>();
if (ns1 != null) {
nsBuilder.add(ReferenceUnion.create(ns1));
}
if (ns2 != null) {
nsBuilder.add(ReferenceUnion.create(ns2));
}
builder.setNameservers(nsBuilder.build());
}
return builder.build();
}
}

View file

@ -0,0 +1,45 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import com.google.appengine.tools.cloudstorage.GcsFileOptions;
import com.google.appengine.tools.cloudstorage.GcsFilename;
import com.google.appengine.tools.cloudstorage.GcsService;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
/** Utility methods for testing Google Cloud Storage code. */
public final class GcsTestingUtils {
/** Slurps a Cloud Storage file into memory. */
public static byte[] readGcsFile(GcsService gcsService, GcsFilename file)
throws IOException {
try (InputStream input = Channels.newInputStream(gcsService.openReadChannel(file, 0))) {
return ByteStreams.toByteArray(input);
}
}
/** Writes a Cloud Storage file. */
public static void writeGcsFile(GcsService gcsService, GcsFilename file, byte[] data)
throws IOException {
gcsService.createOrReplace(file, GcsFileOptions.getDefaultInstance(), ByteBuffer.wrap(data));
}
private GcsTestingUtils() {}
}

View file

@ -0,0 +1,45 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.AbstractVerb.DelegatedVerb;
import com.google.common.truth.FailureStrategy;
import com.google.common.truth.SubjectFactory;
import com.google.domain.registry.model.EppResource;
/** Truth subject for asserting things about {@link EppResource} instances. */
public final class GenericEppResourceSubject
extends AbstractEppResourceSubject<EppResource, GenericEppResourceSubject> {
/** A factory for instances of this subject. */
private static class GenericEppResourceSubjectFactory
extends SubjectFactory<GenericEppResourceSubject, EppResource> {
@Override
public GenericEppResourceSubject getSubject(FailureStrategy strategy, EppResource subject) {
return new GenericEppResourceSubject(strategy, subject);
}
}
public GenericEppResourceSubject(FailureStrategy strategy, EppResource subject) {
super(strategy, checkNotNull(subject));
}
public static DelegatedVerb<GenericEppResourceSubject, EppResource> assertAboutEppResources() {
return assertAbout(new GenericEppResourceSubjectFactory());
}
}

View file

@ -0,0 +1,110 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.io.ByteSource;
import org.junit.rules.ExternalResource;
import java.io.File;
import java.io.IOException;
/**
* GnuPG system command JUnit rule.
*
* <p>This rule creates a isolated environment for running the {@code gpg} command inside system
* integration tests. It reduces a lot of the boilerplate of setting up the shell environment and
* importing your keyrings into a temporary config folder.
*
* @see ExternalResource
*/
public final class GpgSystemCommandRule extends ExternalResource {
private static final File DEV_NULL = new File("/dev/null");
private static final String TEMP_FILE_PREFIX = "gpgtest";
private File cwd = DEV_NULL;
private File conf = DEV_NULL;
private String[] env = {};
private final ByteSource publicKeyring;
private final ByteSource privateKeyring;
private final Runtime runtime = Runtime.getRuntime();
/** Constructs a new {@link GpgSystemCommandRule} instance. */
public GpgSystemCommandRule(ByteSource publicKeyring, ByteSource privateKeyring) {
this.publicKeyring = checkNotNull(publicKeyring, "publicKeyring");
this.privateKeyring = checkNotNull(privateKeyring, "privateKeyring");
}
/** Returns the temporary directory from which commands are run. */
public File getCwd() {
checkState(cwd != DEV_NULL);
return cwd;
}
/** Returns the temporary directory in which GnuPG configs are stored. */
public File getConf() {
checkState(conf != DEV_NULL);
return conf;
}
/**
* Runs specified system command and arguments within the GPG testing environment.
*
* @throws IOException
* @see Runtime#exec(String[])
*/
public final Process exec(String... args) throws IOException {
checkState(cwd != DEV_NULL);
checkArgument(args.length > 0, "args");
return runtime.exec(args, env, cwd);
}
@Override
protected void before() throws IOException, InterruptedException {
checkState(cwd == DEV_NULL);
cwd = File.createTempFile(TEMP_FILE_PREFIX, "", null);
cwd.delete();
cwd.mkdir();
conf = new File(cwd, ".gnupg");
conf.mkdir();
conf.setReadable(true, true);
env = new String[] {
"PATH=" + System.getenv("PATH"),
"GNUPGHOME=" + conf.getAbsolutePath(),
};
Process pid = exec("gpg", "--import");
publicKeyring.copyTo(pid.getOutputStream());
pid.getOutputStream().close();
assertWithMessage("Failed to import public keyring").that(pid.waitFor()).isEqualTo(0);
pid = exec("gpg", "--allow-secret-key-import", "--import");
privateKeyring.copyTo(pid.getOutputStream());
pid.getOutputStream().close();
assertWithMessage("Failed to import private keyring").that(pid.waitFor()).isEqualTo(0);
}
@Override
protected void after() {
cwd = DEV_NULL;
conf = DEV_NULL;
}
}

View file

@ -0,0 +1,106 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.base.Optional;
import com.google.common.truth.AbstractVerb.DelegatedVerb;
import com.google.common.truth.FailureStrategy;
import com.google.common.truth.Subject;
import com.google.domain.registry.model.domain.Period;
import com.google.domain.registry.model.reporting.HistoryEntry;
import com.google.domain.registry.testing.TruthChainer.And;
import java.util.Objects;
/** Utility methods for asserting things about {@link HistoryEntry} instances. */
public class HistoryEntrySubject extends Subject<HistoryEntrySubject, HistoryEntry> {
private String customDisplaySubject;
/** A factory for instances of this subject. */
private static class SubjectFactory
extends ReflectiveSubjectFactory<HistoryEntry, HistoryEntrySubject>{}
public HistoryEntrySubject(FailureStrategy strategy, HistoryEntry subject) {
super(strategy, subject);
}
@Override
public String getDisplaySubject() {
return Optional.fromNullable(customDisplaySubject).or(super.getDisplaySubject());
}
public HistoryEntrySubject withCustomDisplaySubject(String customDisplaySubject) {
this.customDisplaySubject = customDisplaySubject;
return this;
}
public And<HistoryEntrySubject> hasType(HistoryEntry.Type type) {
return hasValue(type, getSubject().getType(), "has type");
}
public And<HistoryEntrySubject> hasClientId(String clientId) {
return hasValue(clientId, getSubject().getClientId(), "has client ID");
}
public And<HistoryEntrySubject> hasPeriod() {
if (getSubject().getPeriod() == null) {
fail("has a period");
}
return new And<HistoryEntrySubject>(this);
}
public And<HistoryEntrySubject> hasPeriodYears(int years) {
return hasPeriod().and()
.hasValue(Period.Unit.YEARS, getSubject().getPeriod().getUnit(), "has period in").and()
.hasValue(years, getSubject().getPeriod().getValue(), "has period length");
}
public And<HistoryEntrySubject> hasNoXml() {
if (getSubject().getXmlBytes() != null) {
fail("has no xml");
}
return new And<HistoryEntrySubject>(this);
}
public And<HistoryEntrySubject> hasMetadataReason(String reason) {
return hasValue(reason, getSubject().getReason(), "has metadata reason");
}
public And<HistoryEntrySubject> hasMetadataRequestedByRegistrar(
boolean requestedByRegistrar) {
if (getSubject().getRequestedByRegistrar() != requestedByRegistrar) {
fail("has metadata requestedByRegistrar with value", requestedByRegistrar);
}
return new And<HistoryEntrySubject>(this);
}
protected void failWithBadResults(String dualVerb, Object expected, Object actual) {
failWithBadResults(dualVerb, expected, dualVerb, actual);
}
protected <E> And<HistoryEntrySubject> hasValue(E expected, E actual, String verb) {
if (!Objects.equals(expected, actual)) {
failWithBadResults(verb, expected, actual);
}
return new And<HistoryEntrySubject>(this);
}
public static DelegatedVerb<HistoryEntrySubject, HistoryEntry> assertAboutHistoryEntries() {
return assertAbout(new SubjectFactory());
}
}

View file

@ -0,0 +1,39 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.AbstractVerb.DelegatedVerb;
import com.google.common.truth.FailureStrategy;
import com.google.domain.registry.model.host.HostResource;
/** Truth subject for asserting things about {@link HostResource} instances. */
public final class HostResourceSubject
extends AbstractEppResourceSubject<HostResource, HostResourceSubject> {
/** A factory for instances of this subject. */
private static class SubjectFactory
extends ReflectiveSubjectFactory<HostResource, HostResourceSubject>{}
public HostResourceSubject(FailureStrategy strategy, HostResource subject) {
super(strategy, checkNotNull(subject));
}
public static DelegatedVerb<HostResourceSubject, HostResource> assertAboutHosts() {
return assertAbout(new SubjectFactory());
}
}

View file

@ -0,0 +1,174 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkState;
import org.junit.rules.ExternalResource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/**
* JUnit Rule for overriding {@code private static} fields during a test.
*
* <p>This rule uses reflection to change the value of a field while your test
* is running and then restore it to its original value after it's done (even
* if the test fails). The injection will work even if the field is marked
* {@code private} (but not if it's {@code final}). The downside is that if you
* rename the field in the future, Eclipse refactoring won't be smart enough to
* update the injection site.
*
* <p>We encourage you to consider using
* {@link com.google.domain.registry.util.NonFinalForTesting &#064;NonFinalForTesting}
* to document your injected fields.
*
* <p>This class is a horrible evil hack, but it alleviates you of the toil of
* having to break encapsulation by making your fields non-{@code private}, using
* the {@link com.google.common.annotations.VisibleForTesting &#064;VisibleForTesting}
* annotation to document why you've reduced visibility, creating a temporary field
* to store the old value, and then writing an {@link org.junit.After &#064;After}
* method to restore it. So sometimes it feels good to be evil; but hopefully one
* day we'll be able to delete this class and do things <i>properly</i> with
* <a href="http://square.github.io/dagger/">Dagger</a> dependency injection.
*
* <p>You use this class by declaring it as a {@link org.junit.Rule &#064;Rule}
* field and then calling {@link #setStaticField} from either your {@link
* org.junit.Test &#064;Test} or {@link org.junit.Before &#064;Before} methods. For
* example:
*
* <pre>
* // Doomsday.java
* public class Doomsday {
*
* private static Clock clock = new SystemClock();
*
* public long getTime() {
* return clock.currentTimeMillis();
* }
* }
*
* // DoomsdayTest.java
* &#064;RunWith(JUnit4.class)
* public class DoomsdayTest {
*
* &#064;Rule
* public InjectRule inject = new InjectRule();
*
* private final FakeClock clock = new FakeClock();
*
* &#064;Before
* public void before() {
* inject.setStaticField(Doomsday.class, "clock", clock);
* }
*
* &#064;Test
* public void test() {
* clock.advanceBy(666L);
* Doomsday doom = new Doomsday();
* assertEquals(666L, doom.getTime());
* }
* }
* </pre>
*
* @see com.google.domain.registry.util.NonFinalForTesting
* @see org.junit.rules.ExternalResource
*/
public class InjectRule extends ExternalResource {
private static class Change {
private final Field field;
@Nullable private final Object oldValue;
@Nullable private final Object newValue;
Change(Field field, @Nullable Object oldValue, @Nullable Object newValue) {
this.field = field;
this.oldValue = oldValue;
this.newValue = newValue;
}
}
private final List<Change> changes = new ArrayList<>();
private final Set<Field> injected = new HashSet<>();
/**
* Sets a static field and be restores its current value after the test completes.
*
* <p>The field is allowed to be {@code private}, but it most not be {@code final}.
*
* <p>This method may be called either from either your {@link org.junit.Before @Before}
* method or from the {@link org.junit.Test @Test} method itself. However you may not
* inject the same field multiple times during the same test.
*
* @throws IllegalArgumentException if the static field could not be found or modified.
* @throws IllegalStateException if the field has already been injected during this test.
*/
public void setStaticField(Class<?> clazz, String fieldName, @Nullable Object newValue) {
Field field;
Object oldValue;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
oldValue = field.get(null);
} catch (NoSuchFieldException
| SecurityException
| IllegalArgumentException
| IllegalAccessException e) {
throw new IllegalArgumentException(String.format(
"Static field not found: %s.%s", clazz.getSimpleName(), fieldName), e);
}
checkState(!injected.contains(field),
"Static field already injected: %s.%s", clazz.getSimpleName(), fieldName);
try {
field.set(null, newValue);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalArgumentException(String.format(
"Static field not settable: %s.%s", clazz.getSimpleName(), fieldName), e);
}
changes.add(new Change(field, oldValue, newValue));
injected.add(field);
}
@Override
protected void after() {
RuntimeException thrown = null;
for (Change change : changes) {
try {
checkState(change.field.get(null).equals(change.newValue),
"Static field value was changed post-injection: %s.%s",
change.field.getDeclaringClass().getSimpleName(), change.field.getName());
change.field.set(null, change.oldValue);
} catch (IllegalArgumentException
| IllegalStateException
| IllegalAccessException e) {
if (thrown == null) {
thrown = new RuntimeException(e);
} else {
thrown.addSuppressed(e);
}
}
}
changes.clear();
injected.clear();
if (thrown != null) {
throw thrown;
}
}
}

View file

@ -0,0 +1,123 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import org.junit.rules.ExternalResource;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.CheckReturnValue;
/** JUnit Rule that uses Mockito to spy on I/O streams to make sure they're healthy. */
public final class IoSpyRule extends ExternalResource {
private boolean checkClosedOnlyOnce;
private boolean checkClosedAtLeastOnce;
private int checkCharIoMaxCalls = -1;
private final List<Closeable> spiedCloseables = new ArrayList<>();
private final List<InputStream> spiedInputStreams = new ArrayList<>();
private final List<OutputStream> spiedOutputStreams = new ArrayList<>();
public IoSpyRule() {}
/**
* Enables check where {@link Closeable#close() close} must be called EXACTLY once.
*
* <p>This is sort of pedantic, since Java's contract for close specifies that it must permit
* multiple calls.
*/
public IoSpyRule checkClosedOnlyOnce() {
checkState(!checkClosedAtLeastOnce, "you're already using checkClosedAtLeastOnce()");
checkClosedOnlyOnce = true;
return this;
}
/** Enables check where {@link Closeable#close() close} must be called at least once. */
public IoSpyRule checkClosedAtLeastOnce() {
checkState(!checkClosedOnlyOnce, "you're already using checkClosedOnlyOnce()");
checkClosedAtLeastOnce = true;
return this;
}
/** Enables check to make sure your streams aren't going too slow with char-based I/O. */
public IoSpyRule checkCharIoMaxCalls(int value) {
checkArgument(value >= 0, "value >= 0");
checkCharIoMaxCalls = value;
return this;
}
/** Adds your {@link Closeable} to the list of streams to check, and returns its mocked self. */
@CheckReturnValue
public <T extends Closeable> T register(T stream) {
T res = spy(stream);
spiedCloseables.add(res);
if (stream instanceof InputStream) {
spiedInputStreams.add((InputStream) res);
}
if (stream instanceof OutputStream) {
spiedOutputStreams.add((OutputStream) res);
}
return res;
}
@Override
protected void after() {
checkState(checkClosedOnlyOnce
|| checkClosedAtLeastOnce
|| checkCharIoMaxCalls != -1,
"At least one check must be enabled.");
try {
check();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
spiedCloseables.clear();
spiedInputStreams.clear();
spiedOutputStreams.clear();
}
}
private void check() throws IOException {
for (Closeable stream : spiedCloseables) {
if (checkClosedAtLeastOnce) {
verify(stream, atLeastOnce()).close();
} else if (checkClosedOnlyOnce) {
verify(stream, times(1)).close();
}
}
if (checkCharIoMaxCalls != -1) {
for (InputStream stream : spiedInputStreams) {
verify(stream, atMost(checkCharIoMaxCalls)).read();
}
for (OutputStream stream : spiedOutputStreams) {
verify(stream, atMost(checkCharIoMaxCalls)).write(anyInt());
}
}
}
}

View file

@ -0,0 +1,36 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import javax.inject.Provider;
/** Helper functions for {@link Provider} */
public final class Providers {
/**
* Returns a {@link Provider} that supplies a constant value.
*
* @deprecated Add {@code //third_party/java/inject_common} as a dependency and then use
* {@link com.google.common.inject.Providers#of} instead.
*/
@Deprecated
public static <T> Provider<T> of(final T instance) {
return new Provider<T>() {
@Override
public T get() {
return instance;
}};
}
}

View file

@ -0,0 +1,36 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import com.google.common.truth.FailureStrategy;
import com.google.common.truth.Subject;
import com.google.common.truth.SubjectFactory;
import com.google.domain.registry.util.TypeUtils.TypeInstantiator;
/** Helper to reduce boilerplate in making new Truth subject classes. */
public class ReflectiveSubjectFactory<T, S extends Subject<S, T>> extends SubjectFactory<S, T> {
@Override
public S getSubject(FailureStrategy strategy, T subject) {
try {
Class<S> sType = new TypeInstantiator<S>(getClass()){}.getExactType();
return sType
.getConstructor(FailureStrategy.class, subject.getClass())
.newInstance(strategy, subject);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,66 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Optional;
import com.google.domain.registry.config.RegistryConfig;
import com.google.domain.registry.config.RegistryEnvironment;
import com.google.domain.registry.config.TestRegistryConfig;
import org.junit.rules.ExternalResource;
/** JUnit Rule for overriding Domain Registry configuration values. */
public final class RegistryConfigRule extends ExternalResource {
private final Optional<RegistryConfig> override;
/** Creates a new instance where {@link #override(RegistryConfig)} will be called as needed. */
public RegistryConfigRule() {
this.override = Optional.absent();
}
/** Creates a new instance where {@code override} will be used for each test method. */
public RegistryConfigRule(RegistryConfig override) {
this.override = Optional.of(override);
}
/** Override registry configuration from inside a test method. */
public void override(RegistryConfig override) {
RegistryEnvironment.overrideConfigurationForTesting(checkNotNull(override));
}
/** Override registry configuration to use TMCH production CA. */
public void useTmchProdCert() {
override(new TestRegistryConfig() {
@Override
public boolean getTmchCaTestingMode() {
return false;
}});
}
@Override
protected void before() throws Exception {
if (override.isPresent()) {
RegistryEnvironment.overrideConfigurationForTesting(override.get());
}
}
@Override
protected void after() {
RegistryEnvironment.overrideConfigurationForTesting(null);
}
}

View file

@ -0,0 +1,42 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
/** Utility class for capturing channel output bytes from google cloud storage library mock. */
public final class SlurpAnswer implements Answer<Integer> {
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
@Override
public Integer answer(@SuppressWarnings("null") InvocationOnMock invocation) throws Exception {
ByteBuffer bytes = (ByteBuffer) invocation.getArguments()[0];
int count = 0;
while (bytes.hasRemaining()) {
out.write(bytes.get());
++count;
}
return count;
}
public byte[] toByteArray() {
return out.toByteArray();
}
}

View file

@ -0,0 +1,63 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.domain.registry.util.FormattingLogger;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import javax.annotation.concurrent.ThreadSafe;
/** Utility class for getting system information in tests. */
@ThreadSafe
public final class SystemInfo {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
private static final LoadingCache<String, Boolean> hasCommandCache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, Boolean>() {
@Override
public Boolean load(String cmd) throws InterruptedException {
try {
Process pid = Runtime.getRuntime().exec(cmd);
pid.getOutputStream().close();
pid.waitFor();
} catch (IOException e) {
logger.warningfmt(e, "%s command not available", cmd);
return false;
}
return true;
}});
/**
* Returns {@code true} if system command can be run from path.
*
* <p><b>Warning:</b> The command is actually run! So there could be side-effects. You might
* need to specify a version flag or something. Return code is ignored.
*
* <p>This result is a memoized. If multiple therads try to get the same result at once, the
* heavy lifting will only be performed by the first thread and the rest will wait.
*
* @throws ExecutionException
*/
public static boolean hasCommand(String cmd) throws ExecutionException {
return hasCommandCache.get(cmd);
}
}

View file

@ -0,0 +1,97 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Optional;
import org.junit.rules.ExternalResource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/**
* JUnit Rule for overriding the values Java system properties during tests.
*/
public final class SystemPropertyRule extends ExternalResource {
/** Class representing a system property key value pair. */
private static class Property {
final String key;
final Optional<String> value;
Property(String key, Optional<String> value) {
this.key = checkNotNull(key, "key");
this.value = checkNotNull(value, "value");
}
void set() {
if (value.isPresent()) {
System.setProperty(key, value.get());
} else {
System.clearProperty(key);
}
}
}
private boolean isRunning = false;
private final List<Property> pendings = new ArrayList<>();
private final Map<String, Property> originals = new HashMap<>();
/**
* Change the value of a system property which is restored to its original value after the test.
*
* <p>It's safe to call this method multiple times with the same {@code key} within a single
* test. Only the truly original property value will be restored at the end.
*
* <p>This method can be called fluently when declaring the Rule field, or within a Test method.
*
* @see java.lang.System#setProperty(String, String)
*/
public SystemPropertyRule override(String key, @Nullable String value) {
if (!originals.containsKey(key)) {
originals.put(key, new Property(key, Optional.fromNullable(System.getProperty(key))));
}
Property property = new Property(key, Optional.fromNullable(value));
if (isRunning) {
property.set();
} else {
pendings.add(property);
}
return this;
}
@Override
protected void before() throws Exception {
checkState(!isRunning);
for (Property pending : pendings) {
pending.set();
}
isRunning = true;
}
@Override
protected void after() {
for (Property original : originals.values()) {
original.set();
}
}
}

View file

@ -0,0 +1,346 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig.getLocalTaskQueue;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Iterables.getFirst;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Multisets.containsOccurrences;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
import static com.google.domain.registry.util.DiffUtils.prettyPrintDeepDiff;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
import com.google.appengine.api.taskqueue.dev.QueueStateInfo;
import com.google.appengine.api.taskqueue.dev.QueueStateInfo.HeaderWrapper;
import com.google.appengine.api.taskqueue.dev.QueueStateInfo.TaskStateInfo;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.net.HttpHeaders;
import com.google.common.net.MediaType;
import com.google.domain.registry.dns.DnsConstants;
import org.joda.time.Duration;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import javax.annotation.Nonnull;
/** Static utility functions for testing task queues. */
public class TaskQueueHelper {
/**
* Matcher to match against the tasks in the task queue. Fields that aren't set are not compared.
*/
public static class TaskMatcher implements Predicate<TaskStateInfo> {
MatchableTaskInfo expected = new MatchableTaskInfo();
public TaskMatcher taskName(String taskName) {
expected.taskName = taskName;
return this;
}
public TaskMatcher url(String url) {
expected.url = url;
return this;
}
public TaskMatcher method(String method) {
expected.method = method;
return this;
}
public TaskMatcher payload(String payload) {
checkState(
expected.params.isEmpty(), "Cannot add a payload to a TaskMatcher with params.");
expected.payload = payload;
return this;
}
public TaskMatcher tag(String tag) {
expected.tag = tag;
return this;
}
public TaskMatcher header(String name, String value) {
// Lowercase for case-insensitive comparison.
expected.headers.put(name.toLowerCase(), value);
return this;
}
public TaskMatcher param(String key, String value) {
checkState(
expected.payload == null, "Cannot add params to a TaskMatcher with a payload.");
expected.params.put(key, value);
return this;
}
public TaskMatcher etaDelta(Duration lowerBound, Duration upperBound) {
checkState(!lowerBound.isShorterThan(Duration.ZERO), "lowerBound must be non-negative.");
checkState(
upperBound.isLongerThan(lowerBound), "upperBound must be greater than lowerBound.");
expected.etaDeltaLowerBound = lowerBound.getStandardSeconds();
expected.etaDeltaUpperBound = upperBound.getStandardSeconds();
return this;
}
/**
* Returns {@code true} if there are not more occurrences in {@code sub} of each of its entries
* than there are in {@code super}.
*/
private static boolean containsEntries(
Multimap<?, ?> superMultimap, Multimap<?, ?> subMultimap) {
return containsOccurrences(
ImmutableMultiset.copyOf(superMultimap.entries()),
ImmutableMultiset.copyOf(subMultimap.entries()));
}
/**
* Returns true if the fields set on the current object match the given task info. This is not
* quite the same contract as {@link #equals}, since it will ignore null fields.
*
* <p>Match fails if any headers or params expected on the TaskMatcher are not found on the
* TaskStateInfo. Note that the inverse is not true (i.e. there may be extra headers on the
* TaskStateInfo).
*/
@Override
public boolean apply(@Nonnull TaskStateInfo info) {
MatchableTaskInfo actual = new MatchableTaskInfo(info);
return (expected.taskName == null || Objects.equals(expected.taskName, actual.taskName))
&& (expected.url == null || Objects.equals(expected.url, actual.url))
&& (expected.method == null || Objects.equals(expected.method, actual.method))
&& (expected.payload == null || Objects.equals(expected.payload, actual.payload))
&& (expected.tag == null || Objects.equals(expected.tag, actual.tag))
&& (expected.etaDeltaLowerBound == null
|| expected.etaDeltaLowerBound <= actual.etaDelta)
&& (expected.etaDeltaUpperBound == null
|| expected.etaDeltaUpperBound >= actual.etaDelta)
&& containsEntries(actual.params, expected.params)
&& containsEntries(actual.headers, expected.headers);
}
@Override
public String toString() {
return Joiner.on('\n')
.withKeyValueSeparator(":\n")
.join(
Maps.transformValues(
expected.toMap(),
new Function<Object, String>() {
@Override
public String apply(Object input) {
return "\t" + String.valueOf(input).replaceAll("\n", "\n\t");
}
}));
}
}
/** Returns the info object for the provided queue name. */
public static QueueStateInfo getQueueInfo(String queueName) {
return getLocalTaskQueue().getQueueStateInfo().get(queueName);
}
/**
* Ensures that the tasks in the named queue are exactly those with the expected property
* values after being transformed with the provided property getter function.
*/
public static void assertTasksEnqueuedWithProperty(
String queueName,
Function<TaskStateInfo, String> propertyGetter,
String... expectedTaskProperties) throws Exception {
// Ordering is irrelevant but duplicates should be considered independently.
assertThat(transform(getQueueInfo(queueName).getTaskInfo(), propertyGetter))
.containsExactly((Object[]) expectedTaskProperties);
}
/** Ensures that the tasks in the named queue are exactly those with the expected names. */
public static void assertTasksEnqueued(String queueName, String... expectedTaskNames)
throws Exception {
Function<TaskStateInfo, String> nameGetter = new Function<TaskStateInfo, String>() {
@Nonnull
@Override
public String apply(@Nonnull TaskStateInfo taskStateInfo) {
return taskStateInfo.getTaskName();
}};
assertTasksEnqueuedWithProperty(queueName, nameGetter, expectedTaskNames);
}
/**
* Ensures that the only tasks in the named queue are exactly those that match the expected
* matchers.
*/
public static void assertTasksEnqueued(String queueName, TaskMatcher... taskMatchers)
throws Exception {
assertTasksEnqueued(queueName, Arrays.asList(taskMatchers));
}
/**
* Ensures that the only tasks in the named queue are exactly those that match the expected
* matchers.
*/
public static void assertTasksEnqueued(String queueName, List<TaskMatcher> taskMatchers)
throws Exception {
QueueStateInfo qsi = getQueueInfo(queueName);
assertThat(qsi.getTaskInfo()).hasSize(taskMatchers.size());
LinkedList<TaskStateInfo> taskInfos = new LinkedList<>(qsi.getTaskInfo());
for (final TaskMatcher taskMatcher : taskMatchers) {
try {
taskInfos.remove(Iterables.find(taskInfos, taskMatcher));
} catch (NoSuchElementException e) {
final Map<String, Object> taskMatcherMap = taskMatcher.expected.toMap();
assert_().fail(
"Task not found in queue %s:\n\n%s\n\nPotential candidate match diffs:\n\n%s",
queueName,
taskMatcher,
FluentIterable.from(taskInfos)
.transform(new Function<TaskStateInfo, String>() {
@Override
public String apply(TaskStateInfo input) {
return prettyPrintDeepDiff(
taskMatcherMap,
Maps.filterKeys(
new MatchableTaskInfo(input).toMap(),
in(taskMatcherMap.keySet())));
}})
.join(Joiner.on('\n')));
}
}
}
/** Empties the task queue. */
public static void clearTaskQueue(String queueName) throws Exception {
getLocalTaskQueue().flushQueue(queueName);
}
/** Asserts at least one task exists in {@code queue}. */
public static void assertAtLeastOneTaskIsEnqueued(String queue) throws Exception {
assertThat(getQueueInfo(queue).getCountTasks()).isGreaterThan(0);
}
/** Ensures that the named queue contains no tasks. */
public static void assertNoTasksEnqueued(String queueName) throws Exception {
assertThat(getQueueInfo(queueName).getCountTasks()).isEqualTo(0);
}
/** Returns the value for the param on a task info, or empty if it is missing. */
private static String getParamFromTaskInfo(TaskStateInfo taskInfo, String paramName) {
return getFirst(UriParameters.parse(taskInfo.getBody()).get(paramName), "");
}
/** Ensures that the DNS queue tasks are exactly those for the expected target names. */
public static void assertDnsTasksEnqueued(String... expectedTaskTargetNames) throws Exception {
assertTasksEnqueuedWithProperty(
DnsConstants.DNS_PULL_QUEUE_NAME,
new Function<TaskStateInfo, String>() {
@Nonnull
@Override
public String apply(@Nonnull TaskStateInfo taskStateInfo) {
return getParamFromTaskInfo(taskStateInfo, DnsConstants.DNS_TARGET_NAME_PARAM);
}},
expectedTaskTargetNames);
}
/** Ensures that the DNS queue does not contain any tasks. */
public static void assertNoDnsTasksEnqueued() throws Exception {
assertNoTasksEnqueued(DnsConstants.DNS_PULL_QUEUE_NAME);
}
/** An adapter to clean up a {@link TaskStateInfo} for ease of matching. */
private static class MatchableTaskInfo {
String taskName;
String method;
String url;
String payload;
String tag;
Double etaDelta;
Long etaDeltaLowerBound;
Long etaDeltaUpperBound;
Multimap<String, String> headers = ArrayListMultimap.create();
Multimap<String, String> params = ArrayListMultimap.create();
MatchableTaskInfo() {}
MatchableTaskInfo(TaskStateInfo info) {
URI uri;
try {
uri = new URI(info.getUrl());
} catch (java.net.URISyntaxException e) {
throw new IllegalArgumentException(e);
}
this.taskName = info.getTaskName();
this.method = info.getMethod();
this.url = uri.getPath();
this.payload = info.getBody();
this.etaDelta = info.getEtaDelta();
if (info.getTagAsBytes() != null) {
this.tag = new String(info.getTagAsBytes(), UTF_8);
}
ImmutableMultimap.Builder<String, String> headerBuilder = ImmutableMultimap.builder();
for (HeaderWrapper header : info.getHeaders()) {
// Lowercase header name for comparison since HTTP
// header names are case-insensitive.
headerBuilder.put(header.getKey().toLowerCase(), header.getValue());
}
this.headers = headerBuilder.build();
ImmutableMultimap.Builder<String, String> inputParams = new ImmutableMultimap.Builder<>();
String query = uri.getQuery();
if (query != null) {
inputParams.putAll(UriParameters.parse(query));
}
if (headers.containsEntry(
HttpHeaders.CONTENT_TYPE.toLowerCase(), MediaType.FORM_DATA.toString())) {
inputParams.putAll(UriParameters.parse(info.getBody()));
}
this.params = inputParams.build();
}
public Map<String, Object> toMap() {
Map<String, Object> builder = new HashMap<>();
builder.put("taskName", taskName);
builder.put("url", url);
builder.put("method", method);
builder.put("headers", headers.asMap());
builder.put("params", params.asMap());
builder.put("payload", payload);
builder.put("tag", tag);
builder.put("etaDelta", etaDelta);
builder.put("etaDeltaLowerBound", etaDeltaLowerBound);
builder.put("etaDeltaUpperBound", etaDeltaUpperBound);
return Maps.filterValues(builder, not(in(asList(null, "", Collections.EMPTY_MAP))));
}
}
}

View file

@ -0,0 +1,42 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.domain.registry.util.CollectionUtils.isNullOrEmpty;
import static com.google.domain.registry.util.ResourceUtils.readResourceUtf8;
import java.util.Map;
import java.util.Map.Entry;
/**
* Contains helper methods for dealing with test data.
*/
public final class TestDataHelper {
/**
* Loads a text file from the "testdata" directory relative to the location of the specified
* context class, and substitutes in values for placeholders of the form <code>%tagname%</code>.
*/
public static String loadFileWithSubstitutions(
Class<?> context, String filename, Map<String, String> substitutions) {
String fileContents = readResourceUtf8(context, "testdata/" + filename);
if (!isNullOrEmpty(substitutions)) {
for (Entry<String, String> entry : substitutions.entrySet()) {
fileContents = fileContents.replaceAll("%" + entry.getKey() + "%", entry.getValue());
}
}
return fileContents;
}
}

View file

@ -0,0 +1,63 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.model.common.EntityGroupRoot;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
/**
* A test model object that can be persisted in any entity group.
*/
@Entity
public class TestObject extends ImmutableObject {
@Parent
Key<EntityGroupRoot> parent;
@Id
String id;
String field;
public String getId() {
return id;
}
public String getField() {
return field;
}
public static TestObject create(String id) {
return create(id, null);
}
public static TestObject create(String id, String field) {
return create(id, field, getCrossTldKey());
}
public static TestObject create(String id, String field, Key<EntityGroupRoot> parent) {
TestObject instance = new TestObject();
instance.id = id;
instance.field = field;
instance.parent = parent;
return instance;
}
}

View file

@ -0,0 +1,59 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkState;
import com.google.domain.registry.flows.SessionMetadata;
import java.util.HashMap;
import java.util.Map;
public class TestSessionMetadata extends SessionMetadata {
private final Map<String, Object> properties = new HashMap<>();
private boolean isValid = true;
private SessionSource sessionSource = SessionSource.NONE;
@Override
protected void setProperty(String key, Object value) {
properties.put(key, value);
}
@Override
protected Object getProperty(String key) {
return properties.get(key);
}
@Override
public void checkValid() {
checkState(isValid, "Session was invalidated");
}
@Override
public void invalidate() {
isValid = false;
}
@Override
public SessionSource getSessionSource() {
return sessionSource;
}
@Override
public void setSessionSource(SessionSource source) {
sessionSource = source;
}
}

View file

@ -0,0 +1,49 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import com.google.common.truth.Subject;
/** Shim classes to enable fluent chained assertions. */
public final class TruthChainer {
/** Add another assertion. */
public static class And<S extends Subject<?, ?>> {
private final S subject;
And(S subject) {
this.subject = subject;
}
public S and() {
return subject;
}
}
/** Move the word "which" to after a parameterized assertion. */
public static class Which<S extends Subject<?, ?>> {
private final S subject;
Which(S subject) {
this.subject = subject;
}
public S which() {
return subject;
}
}
}

View file

@ -0,0 +1,82 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
/**
* Utility class for working with <a href="http://goo.gl/OQEc8">application/x-www-form-urlencoded
* content type</a> data.
*/
public final class UriParameters {
/**
* Constructs a new parameter map populated with parameters parsed from the specified query string
* using the specified encoding.
*
* @param query the query string, e.g., {@code "q=flowers&n=20"}
* @return a mutable parameter map representing the query string
*/
public static ListMultimap<String, String> parse(String query) {
checkNotNull(query);
ArrayListMultimap<String, String> map = ArrayListMultimap.create();
if (!query.isEmpty()) {
int start = 0;
while (start <= query.length()) {
// Find the end of the current parameter.
int ampersandIndex = query.indexOf('&', start);
if (ampersandIndex == -1) {
ampersandIndex = query.length();
}
int equalsIndex = query.indexOf('=', start);
if (equalsIndex > ampersandIndex) {
// Equal is in the next parameter, so this parameter has no value.
equalsIndex = -1;
}
int paramNameEndIndex = (equalsIndex == -1) ? ampersandIndex : equalsIndex;
String name = decodeString(query, start, paramNameEndIndex);
String value = (equalsIndex == -1)
? ""
: decodeString(query, equalsIndex + 1, ampersandIndex);
map.put(name, value);
start = ampersandIndex + 1;
}
}
return map;
}
private static String decodeString(String str, int start, int end) {
try {
return URLDecoder.decode(str.substring(start, end), UTF_8.name());
} catch (IllegalArgumentException iae) {
// According to the javadoc of URLDecoder, when the input string is
// illegal, it could either leave the illegal characters alone or throw
// an IllegalArgumentException! To deal with both consistently, we
// ignore IllegalArgumentException and just return the original string.
return str.substring(start, end);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
private UriParameters() {}
}

View file

@ -0,0 +1,47 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing;
import com.google.auto.value.AutoValue;
/** Container for values passed to {@link AppEngineRule} to set the logged in user for tests. */
@AutoValue
public abstract class UserInfo {
abstract String email();
abstract String authDomain();
abstract String gaeUserId();
abstract boolean isAdmin();
abstract boolean isLoggedIn();
/** Creates a new logged-in non-admin user instance. */
public static UserInfo create(String email, String gaeUserId) {
String authDomain = email.substring(email.indexOf('@') + 1);
return new AutoValue_UserInfo(email, authDomain, gaeUserId, false, true);
}
/** Creates a new logged-in admin user instance. */
public static UserInfo createAdmin(String email, String gaeUserId) {
String authDomain = email.substring(email.indexOf('@') + 1);
return new AutoValue_UserInfo(email, authDomain, gaeUserId, true, true);
}
/** Returns a logged-out user instance. */
public static UserInfo loggedOut() {
return new AutoValue_UserInfo("", "", "", false, false);
}
UserInfo() {}
}

View file

@ -0,0 +1,13 @@
rich,USD 100
richer,USD 1000
silver,USD 588
iridium,USD 13117
gold,USD 24317
platinum,USD 87741
rhodium,USD 88415
diamond,USD 1000000
palladium,USD 877
aluminum,USD 11
copper,USD 15
brass,USD 20
platinum,USD 80000 #this should be ignored as a cheaper duplicate
1 rich USD 100
2 richer USD 1000
3 silver USD 588
4 iridium USD 13117
5 gold USD 24317
6 platinum USD 87741
7 rhodium USD 88415
8 diamond USD 1000000
9 palladium USD 877
10 aluminum USD 11
11 copper USD 15
12 brass USD 20
13 platinum USD 80000 #this should be ignored as a cheaper duplicate

View file

@ -0,0 +1,9 @@
handlers = java.util.logging.ConsoleHandler
.level = INFO
com.google.domain.registry.level = FINE
com.google.appengine.api.datastore.dev.LocalDatastoreService.level = WARNING
com.google.appengine.api.taskqueue.dev.level = WARNING
com.google.apphosting.utils.config.level = WARNING
org.quartz.level = WARNING

View file

@ -0,0 +1,35 @@
package(
default_visibility = ["//java/com/google/domain/registry:registry_project"],
)
load("//java/com/google/testing/builddefs:GenTestRules.bzl", "GenTestRules")
java_library(
name = "mapreduce",
srcs = glob(["*.java"]),
deps = [
"//java/com/google/common/base",
"//java/com/google/common/collect",
"//java/com/google/domain/registry/model",
"//java/com/google/domain/registry/util",
"//javatests/com/google/domain/registry/testing",
"//third_party/java/appengine:appengine-api",
"//third_party/java/appengine:appengine-stubs",
"//third_party/java/appengine:appengine-testing",
"//third_party/java/appengine_mapreduce2:appengine_mapreduce",
"//third_party/java/appengine_pipeline",
"//third_party/java/joda_time",
"//third_party/java/jsr305_annotations",
"//third_party/java/junit",
"//third_party/java/mockito",
"//third_party/java/servlet/servlet_api",
"//third_party/java/truth",
],
)
GenTestRules(
name = "GeneratedTestRules",
test_files = glob(["*Test.java"]),
deps = [":groups"],
)

View file

@ -0,0 +1,218 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing.mapreduce;
import static com.google.common.truth.Truth.assertThat;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.appengine.api.blobstore.dev.LocalBlobstoreService;
import com.google.appengine.api.taskqueue.dev.LocalTaskQueue;
import com.google.appengine.api.taskqueue.dev.QueueStateInfo;
import com.google.appengine.api.taskqueue.dev.QueueStateInfo.HeaderWrapper;
import com.google.appengine.api.taskqueue.dev.QueueStateInfo.TaskStateInfo;
import com.google.appengine.tools.development.ApiProxyLocal;
import com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig;
import com.google.appengine.tools.mapreduce.MapReduceServlet;
import com.google.appengine.tools.mapreduce.impl.shardedjob.ShardedJobHandler;
import com.google.appengine.tools.pipeline.impl.servlets.PipelineServlet;
import com.google.appengine.tools.pipeline.impl.servlets.TaskHandler;
import com.google.apphosting.api.ApiProxy;
import com.google.common.base.CharMatcher;
import com.google.domain.registry.testing.AppEngineRule;
import com.google.domain.registry.testing.FakeClock;
import com.google.domain.registry.util.FormattingLogger;
import org.junit.Before;
import org.junit.Rule;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Base test class for mapreduces. Adapted from EndToEndTestCase with some modifications that
* allow it to work with the Domain Registry project, most notably inside knowledge of our
* routing paths and our Datastore/Task Queue configurations.
*
* <p>See https://github.com/GoogleCloudPlatform/appengine-mapreduce/blob/master/java/src/test/java/com/google/appengine/tools/mapreduce/EndToEndTestCase.java
*
* @param <T> The type of the Action class that implements the mapreduce.
*/
public abstract class MapreduceTestCase<T> {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
protected T action;
private final MapReduceServlet mrServlet = new MapReduceServlet();
private final PipelineServlet pipelineServlet = new PipelineServlet();
private LocalTaskQueue taskQueue;
@Rule
public final AppEngineRule appEngine = AppEngineRule.builder()
.withDatastore()
.withLocalModules()
.withTaskQueue()
.build();
@Before
public void setUp() throws Exception {
taskQueue = LocalTaskQueueTestConfig.getLocalTaskQueue();
ApiProxyLocal proxy = (ApiProxyLocal) ApiProxy.getDelegate();
// Creating files is not allowed in some test execution environments, so don't.
proxy.setProperty(LocalBlobstoreService.NO_STORAGE_PROPERTY, "true");
}
protected List<QueueStateInfo.TaskStateInfo> getTasks(String queueName) {
return taskQueue.getQueueStateInfo().get(queueName).getTaskInfo();
}
protected void executeTask(String queueName, QueueStateInfo.TaskStateInfo taskStateInfo)
throws Exception {
logger.finefmt("Executing task %s with URL %s",
taskStateInfo.getTaskName(), taskStateInfo.getUrl());
// Hack to allow for deferred tasks. Exploits knowing how they work.
if (taskStateInfo.getUrl().endsWith("__deferred__")) {
ObjectInputStream oin =
new ObjectInputStream(new ByteArrayInputStream(taskStateInfo.getBodyAsBytes()));
Runnable object = (Runnable) oin.readObject();
object.run();
return;
}
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
// Strip off routing paths that are handled in web.xml in non-test scenarios.
String pathInfo = taskStateInfo.getUrl();
if (pathInfo.startsWith("/_dr/mapreduce/")) {
pathInfo = pathInfo.replace("/_dr/mapreduce", "");
} else if (pathInfo.startsWith("/")) {
pathInfo = pathInfo.replace("/_ah/", "");
pathInfo = pathInfo.substring(pathInfo.indexOf('/'));
} else {
pathInfo = "/" + pathInfo;
}
when(request.getPathInfo()).thenReturn(pathInfo);
when(request.getHeader("X-AppEngine-QueueName")).thenReturn(queueName);
when(request.getHeader("X-AppEngine-TaskName")).thenReturn(taskStateInfo.getTaskName());
// Pipeline looks at this header but uses the value only for diagnostic messages
when(request.getIntHeader(TaskHandler.TASK_RETRY_COUNT_HEADER)).thenReturn(-1);
for (HeaderWrapper header : taskStateInfo.getHeaders()) {
int value = parseAsQuotedInt(header.getValue());
when(request.getIntHeader(header.getKey())).thenReturn(value);
logger.finefmt("header: %s=%s", header.getKey(), header.getValue());
when(request.getHeader(header.getKey())).thenReturn(header.getValue());
}
Map<String, String> parameters = decodeParameters(taskStateInfo.getBody());
for (String name : parameters.keySet()) {
when(request.getParameter(name)).thenReturn(parameters.get(name));
}
when(request.getParameterNames()).thenReturn(Collections.enumeration(parameters.keySet()));
if (taskStateInfo.getMethod().equals("POST")) {
if (taskStateInfo.getUrl().startsWith(PipelineServlet.BASE_URL)) {
pipelineServlet.doPost(request, response);
} else {
mrServlet.doPost(request, response);
}
} else {
throw new UnsupportedOperationException();
}
}
private int parseAsQuotedInt(String str) {
try {
return Integer.parseInt(CharMatcher.is('"').trimFrom(str));
} catch (NumberFormatException e) {
return -1;
}
}
protected void executeTasksUntilEmpty() throws Exception {
executeTasksUntilEmpty("default");
}
protected void executeTasksUntilEmpty(String queueName) throws Exception {
executeTasksUntilEmpty(queueName, null);
}
/**
* Executes mapreduce tasks, increment the clock between each task.
*
* <p>Incrementing the clock between tasks is important if tasks have transactions inside the
* mapper or reducer, which don't have access to the fake clock.
*/
protected void
executeTasksUntilEmpty(String queueName, @Nullable FakeClock clock) throws Exception {
while (true) {
ofy().clearSessionCache();
// We have to re-acquire task list every time, because local implementation returns a copy.
List<QueueStateInfo.TaskStateInfo> taskInfo =
taskQueue.getQueueStateInfo().get(queueName).getTaskInfo();
if (taskInfo.isEmpty()) {
break;
}
QueueStateInfo.TaskStateInfo taskStateInfo = taskInfo.get(0);
taskQueue.deleteTask(queueName, taskStateInfo.getTaskName());
executeTask(queueName, taskStateInfo);
if (clock != null) {
clock.advanceOneMilli();
}
}
}
protected TaskStateInfo grabNextTaskFromQueue(String queueName) {
List<TaskStateInfo> taskInfo = getTasks(queueName);
assertThat(taskInfo).isNotEmpty();
TaskStateInfo taskStateInfo = taskInfo.get(0);
taskQueue.deleteTask(queueName, taskStateInfo.getTaskName());
return taskStateInfo;
}
// Sadly there's no way to parse query string with JDK. This is a good enough approximation.
private static Map<String, String> decodeParameters(String requestBody)
throws UnsupportedEncodingException {
Map<String, String> result = new HashMap<>();
String[] params = requestBody.split("&");
for (String param : params) {
String[] pair = param.split("=");
String name = pair[0];
String value = URLDecoder.decode(pair[1], "UTF-8");
if (result.containsKey(name)) {
throw new IllegalArgumentException("Duplicate parameter: " + requestBody);
}
result.put(name, value);
}
return result;
}
protected String getTaskId(TaskStateInfo taskStateInfo) throws UnsupportedEncodingException {
return decodeParameters(taskStateInfo.getBody()).get(ShardedJobHandler.TASK_ID_PARAM);
}
}

View file

@ -0,0 +1,18 @@
// Copyright 2016 Google Inc. 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.
// TODO(b/19014308): Move the SRE testing package under java/ somewhere else so we
// don't need this package-info.java file under javatests/
@javax.annotation.ParametersAreNonnullByDefault
package com.google.domain.registry.testing;

View file

@ -0,0 +1,20 @@
package(
default_visibility = ["//java/com/google/domain/registry:registry_project"],
)
java_library(
name = "sftp",
srcs = glob(["*.java"]),
deps = [
"//java/com/google/common/base",
"//java/com/google/common/collect",
"//java/com/google/domain/registry/util",
"//third_party/java/apache_sshd",
"//third_party/java/bouncycastle",
"//third_party/java/bouncycastle_bcpg",
"//third_party/java/ftpserver",
"//third_party/java/jsr305_annotations",
"//third_party/java/junit",
],
)

View file

@ -0,0 +1,68 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing.sftp;
import static com.google.common.base.Preconditions.checkState;
import com.google.domain.registry.util.NetworkUtils;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.sshd.server.session.SessionFactory;
import org.junit.rules.ExternalResource;
import java.io.File;
import java.io.IOException;
import javax.annotation.Nullable;
/**
* JUnit Rule for creating an in-process {@link TestSftpServer SFTP Server}.
*
* @see TestSftpServer
*/
public final class SftpServerRule extends ExternalResource {
@Nullable
private FtpServer server;
/**
* Starts an SFTP server on a randomly selected port.
*
* @return the port on which the server is listening
*/
public int serve(String user, String pass, File home) throws IOException, FtpException {
checkState(server == null, "You already have an SFTP server!");
int port = NetworkUtils.pickUnusedPort();
server = createSftpServer(user, pass, home, port);
return port;
}
@Override
protected void after() {
if (server != null) {
server.stop();
server = null;
}
}
private static FtpServer createSftpServer(String user, String pass, File home, int port)
throws FtpException {
FtpServer server =
TestSftpServer.createSftpServer(user, pass, null, port, home, new SessionFactory());
server.start();
return server;
}
}

View file

@ -0,0 +1,349 @@
// Copyright 2016 Google Inc. 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 com.google.domain.registry.testing.sftp;
import com.google.common.collect.ImmutableList;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.sshd.SshServer;
import org.apache.sshd.common.Channel;
import org.apache.sshd.common.Cipher;
import org.apache.sshd.common.Compression;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.Mac;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.Session;
import org.apache.sshd.common.Signature;
import org.apache.sshd.common.cipher.AES128CBC;
import org.apache.sshd.common.cipher.AES192CBC;
import org.apache.sshd.common.cipher.AES256CBC;
import org.apache.sshd.common.cipher.BlowfishCBC;
import org.apache.sshd.common.cipher.TripleDESCBC;
import org.apache.sshd.common.compression.CompressionNone;
import org.apache.sshd.common.mac.HMACMD5;
import org.apache.sshd.common.mac.HMACMD596;
import org.apache.sshd.common.mac.HMACSHA1;
import org.apache.sshd.common.mac.HMACSHA196;
import org.apache.sshd.common.random.BouncyCastleRandom;
import org.apache.sshd.common.random.SingletonRandomFactory;
import org.apache.sshd.common.signature.SignatureDSA;
import org.apache.sshd.common.signature.SignatureRSA;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.FileSystemFactory;
import org.apache.sshd.server.FileSystemView;
import org.apache.sshd.server.ForwardingAcceptorFactory;
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.SshFile;
import org.apache.sshd.server.channel.ChannelDirectTcpip;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.filesystem.NativeFileSystemFactory;
import org.apache.sshd.server.filesystem.NativeSshFile;
import org.apache.sshd.server.kex.DHG1;
import org.apache.sshd.server.kex.DHG14;
import org.apache.sshd.server.session.DefaultForwardingAcceptorFactory;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.session.SessionFactory;
import org.apache.sshd.server.sftp.SftpSubsystem;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.Security;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/** In-process SFTP server using Apache SSHD. */
public class TestSftpServer implements FtpServer {
private static final Logger logger = Logger.getLogger(TestSftpServer.class.getName());
private static SingletonRandomFactory secureRandomFactory;
static {
Security.addProvider(new BouncyCastleProvider());
secureRandomFactory = new SingletonRandomFactory(new BouncyCastleRandom.Factory());
}
private static final String HOST_KEY = ""
+ "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEowIBAAKCAQEAx7uuoDyMR3c+sIg0fPfBeyoUjPa6hh3yN5S4/HLEOOaXcA2c\n"
+ "uhNm8WwIXF/FwC32CJHZcCC3XhNeQDIpEt5wj9NHr++pjYWjp4Ue+n/2QbDO4LKB\n"
+ "fb67zRbL++BZNswBKYwmzaiHoyWYERj+ZrZpf7hqHnKTAs4NIUCkhUvUHQzgdfT6\n"
+ "vfEYv2HrM2VVVaqpUALrkogZHqQYfLA+InHXXZ3x+GmdSBCxaxUHHZypwETv+yBJ\n"
+ "YAP+9Ofcmu3mpedK/3o3KJ2pD4OXwoXAtbHDOfnJMJylCKcCT3aodpdULSf4A8Zm\n"
+ "o4fxppQCbGUw43D7u18he5qNdLDkgCV8iXewbQIDAQABAoIBAH/Fs9e0BDVvtj3u\n"
+ "VE2hnTeyWsU2zWog3CPsU07ECH0yHqzDOIDdCpzk9JBLgFEJ1fvzebs+Yq+fCktd\n"
+ "C2OTw0Ru78xAMCJl3KS9B21O0PWDK0UZTLdpffCcZc/y3H+ukAvJKcWky2h2E0rU\n"
+ "x2JjzSe0jMZ/m0ZPFJ0yIk1XjhEqWgp3OCP9EHaO82+GM8UTuoBrfOs8gcv6aqiO\n"
+ "L06BU6a6i75chUtZbSadaUnZOE8tIyXit6p63bBHp2oN4D8FiOPBuTDwFEI4zrA4\n"
+ "vNcfRvKQWnQjlWm91BCajNxVqWi7XtWK7ikmwefZWNcd83RSf6vfb8ThpjwHmBaE\n"
+ "dbbL9i0CgYEA+KwB5qiaRxryBI1xqQaH6HiQ5WtW06ft43l2FrTZGPocjyF7KyWn\n"
+ "lS5q0J4tdSSkNfXqhT8aosm3VjfIxoDuQQO0ptD+km5qe1xu/qxLHJYd5FP67X9O\n"
+ "66e8sxcDSuKSvZ6wNeKNOL5SC7dDLCuMZoRTvXxGneg9a1w2x1MVrZsCgYEAzZ56\n"
+ "cqqNvavj4x2yDG+4oAB4/sQvVFK2+PopFpO5w8ezLDVfnoIv56gfmqMN9UdZH8Ew\n"
+ "PJhuEFRcdePo2ZnNjsWf9NhniJSMsEPTIc5qOPyoo5DcQM6DU8f4HC236uR+5P0h\n"
+ "jLfKRpvZl+N3skJi98QQr9PeLyb0sM8zRbZ0fpcCgYA9nuIZtk4EsLioSCSSLfwf\n"
+ "r0C4mRC7AjIA3GhW2Bm0BsZs8W8EEiCk5wuxBoFdNec7N+UVf72p+TJlOw2Vov1n\n"
+ "PvPVIpTy1EmuqAkZMriqLMjbe7QChjmYS8iG2H0IYXzbYCdqMumr1f2eyZrrpx7z\n"
+ "iHb3zYPyPUp7AC7S1dPZYQKBgQCK0p+LQVk3IJFYalkmiltVM1x9bUkjHkFIseUB\n"
+ "yDUYWIDArTxkkTL0rY7A4atv2X7zsIP3tVZCEiLmuTwhhfTBmu3W6jBkhx7Bdtla\n"
+ "LrmKxhK5c/kwi/0gmJcLt1Y/8Ys24SxAjGm16E0tfjb3FFkrPKWjgGC25w83PH06\n"
+ "aOgX+wKBgBLkLh/rwpiD4e8ZVjGuCn9A0H/2KZknXyNbkVjPho0FXBKIlfMa6c34\n"
+ "fRLDvHVZ0xo4dQxp38Wg+ZzIwGoAHhuJpSsfMsWKVXwNwV4iMEt2zyZRomui3TzT\n"
+ "+8awtDyALwvL05EBuxctT3iFGQcUj/fNCi0PeLoTSscdH2pdvHTb\n"
+ "-----END RSA PRIVATE KEY-----\n";
private static final String KEY_TYPE = "ssh-rsa";
private static final KeyPair HOST_KEY_PAIR = createKeyPair(HOST_KEY);
@Nullable
private static KeyPair createKeyPair(String key) {
try (PEMParser pemParser = new PEMParser(new StringReader(key))) {
PEMKeyPair pemPair = (PEMKeyPair) pemParser.readObject();
KeyPair result = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair);
logger.info("Read key pair " + result);
return result;
} catch (IOException e) {
logger.log(Level.SEVERE, "Couldn't read key pair from string(!)", e);
return null;
}
}
// Apache provides a NativeFileSystemView, but it assumes that the root
// directory you want is /home/username. Yep.
// So reuse as much as we can.
private static class TestFileSystemView implements FileSystemView {
private final String userName;
private final File home;
public TestFileSystemView(String userName, File home) {
this.userName = userName;
this.home = home;
}
@Override
public SshFile getFile(SshFile arg1, String arg2) {
return null;
}
@Override
public SshFile getFile(String fileName) {
File file = new File(home, fileName);
// Work around demands of NativeSshFile constructor.
String absolutePath = fileName.equals(".") ? "/" : fileName;
return new TestSshFile(absolutePath, file, userName, home);
}
}
private static class TestSshFile extends NativeSshFile {
// Purely an end-run around the protected constructor
@SuppressWarnings("unused")
TestSshFile(String fileName, File file, String userName, File home) {
super(fileName, file, userName);
}
}
public static FtpServer createSftpServer(
final String authorizedUser,
@Nullable final String authorizedPassword,
@Nullable final PublicKey authorizedPublicKey,
int port,
final File home,
SessionFactory sessionFactory) {
final SshServer server = setUpDefaultServer();
server.setPort(port);
server.setSessionFactory(sessionFactory);
NamedFactory<Command> sftpSubsystemFactory = new SftpSubsystem.Factory();
server.setSubsystemFactories(ImmutableList.of(sftpSubsystemFactory));
if (authorizedPassword != null) {
PasswordAuthenticator passwordAuthenticator = new PasswordAuthenticator() {
@Override
public boolean authenticate(String username, String password, ServerSession session) {
return username.equals(authorizedUser) && password.equals(authorizedPassword);
}
};
server.setPasswordAuthenticator(passwordAuthenticator);
}
// This authenticator checks that the user is presenting the right key. If authenticate
// returns true, then the server will make sure that the user can prove they have that key.
// Not that you would know this from the Apache javadocs.
if (authorizedPublicKey != null) {
PublickeyAuthenticator publicKeyAuthenticator = new PublickeyAuthenticator() {
@Override
public boolean authenticate(String username, PublicKey publicKey, ServerSession session) {
return Arrays.equals(publicKey.getEncoded(), authorizedPublicKey.getEncoded());
}
};
server.setPublickeyAuthenticator(publicKeyAuthenticator);
}
FileSystemFactory fileSystemFactory = new FileSystemFactory() {
@Override
public FileSystemView createFileSystemView(Session session) {
return new TestFileSystemView("anyone", home);
}
};
server.setFileSystemFactory(fileSystemFactory);
KeyPairProvider keyPairProvider = new KeyPairProvider() {
@Override
public KeyPair loadKey(String type) {
return (type.equals(KEY_TYPE)) ? HOST_KEY_PAIR : null;
}
@Override
public String getKeyTypes() {
return KEY_TYPE;
}
};
server.setKeyPairProvider(keyPairProvider);
return new TestSftpServer(server);
}
private final SshServer server;
private boolean stopped = true;
private TestSftpServer(SshServer server) {
this.server = server;
}
@Override
public void suspend() {
// NOP
}
@Override
public synchronized void stop() {
try {
logger.info("Stopping server");
server.stop();
stopped = true;
} catch (InterruptedException e) {
logger.log(Level.WARNING, "Server shutdown interrupted", e);
}
}
@Override
public synchronized void start() throws FtpException {
try {
logger.info("Starting server");
server.start();
stopped = false;
} catch (IOException e) {
logger.log(Level.WARNING, "Couldn't start server", e);
throw new FtpException("Couldn't start server", e);
}
}
@Override
public void resume() {
// NOP
}
@Override
public boolean isSuspended() {
return false;
}
@Override
public boolean isStopped() {
return stopped;
}
// More almost-cut-and-paste from Apache. Their version of this method
// creates a new "singleton" random number generator each time it's called,
// which in turn waits for enough securely random bits to be available from
// the system. Certainly for test purposes it's good enough for everyone
// to share the same random seed. SuppressWarnings because Apache is a bit
// more lax about generics than we are.
private static SshServer setUpDefaultServer() {
SshServer sshd = new SshServer();
// DHG14 uses 2048 bits key which are not supported by the default JCE provider
sshd.setKeyExchangeFactories(Arrays.asList(
new DHG14.Factory(),
new DHG1.Factory()));
sshd.setRandomFactory(secureRandomFactory);
setUpDefaultCiphers(sshd);
// Compression is not enabled by default
// sshd.setCompressionFactories(Arrays.<NamedFactory<Compression>>asList(
// new CompressionNone.Factory(),
// new CompressionZlib.Factory(),
// new CompressionDelayedZlib.Factory()));
sshd.setCompressionFactories(Arrays.<NamedFactory<Compression>>asList(
new CompressionNone.Factory()));
sshd.setMacFactories(Arrays.<NamedFactory<Mac>>asList(
new HMACMD5.Factory(),
new HMACSHA1.Factory(),
new HMACMD596.Factory(),
new HMACSHA196.Factory()));
sshd.setChannelFactories(Arrays.<NamedFactory<Channel>>asList(
new ChannelSession.Factory(),
new ChannelDirectTcpip.Factory()));
sshd.setSignatureFactories(Arrays.<NamedFactory<Signature>>asList(
new SignatureDSA.Factory(),
new SignatureRSA.Factory()));
sshd.setFileSystemFactory(new NativeFileSystemFactory());
ForwardingAcceptorFactory faf = new DefaultForwardingAcceptorFactory();
sshd.setTcpipForwardNioSocketAcceptorFactory(faf);
sshd.setX11ForwardNioSocketAcceptorFactory(faf);
return sshd;
}
private static void setUpDefaultCiphers(SshServer sshd) {
List<NamedFactory<Cipher>> avail = new LinkedList<>();
avail.add(new AES128CBC.Factory());
avail.add(new TripleDESCBC.Factory());
avail.add(new BlowfishCBC.Factory());
avail.add(new AES192CBC.Factory());
avail.add(new AES256CBC.Factory());
for (Iterator<NamedFactory<Cipher>> i = avail.iterator(); i.hasNext();) {
final NamedFactory<Cipher> f = i.next();
try {
final Cipher c = f.create();
final byte[] key = new byte[c.getBlockSize()];
final byte[] iv = new byte[c.getIVSize()];
c.init(Cipher.Mode.Encrypt, key, iv);
} catch (Exception e) {
i.remove();
}
}
sshd.setCipherFactories(avail);
}
}