mirror of
https://github.com/google/nomulus.git
synced 2025-07-31 15:06:29 +02:00
Import code from internal repository to git
This commit is contained in:
commit
0ef0c933d2
2490 changed files with 281594 additions and 0 deletions
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
409
javatests/com/google/domain/registry/testing/AppEngineRule.java
Normal file
409
javatests/com/google/domain/registry/testing/AppEngineRule.java
Normal 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()));
|
||||
}
|
||||
}
|
56
javatests/com/google/domain/registry/testing/BUILD
Normal file
56
javatests/com/google/domain/registry/testing/BUILD
Normal 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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
50
javatests/com/google/domain/registry/testing/EppLoader.java
Normal file
50
javatests/com/google/domain/registry/testing/EppLoader.java
Normal 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);
|
||||
}
|
||||
}
|
107
javatests/com/google/domain/registry/testing/ExceptionRule.java
Normal file
107
javatests/com/google/domain/registry/testing/ExceptionRule.java
Normal 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;
|
||||
}
|
||||
}
|
55
javatests/com/google/domain/registry/testing/FailAnswer.java
Normal file
55
javatests/com/google/domain/registry/testing/FailAnswer.java
Normal 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());
|
||||
}
|
||||
}
|
69
javatests/com/google/domain/registry/testing/FakeClock.java
Normal file
69
javatests/com/google/domain/registry/testing/FakeClock.java
Normal 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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
105
javatests/com/google/domain/registry/testing/FakeResponse.java
Normal file
105
javatests/com/google/domain/registry/testing/FakeResponse.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
174
javatests/com/google/domain/registry/testing/InjectRule.java
Normal file
174
javatests/com/google/domain/registry/testing/InjectRule.java
Normal 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 @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 @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 @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 @Rule}
|
||||
* field and then calling {@link #setStaticField} from either your {@link
|
||||
* org.junit.Test @Test} or {@link org.junit.Before @Before} methods. For
|
||||
* example:
|
||||
*
|
||||
* <pre>
|
||||
* // Doomsday.java
|
||||
* public class Doomsday {
|
||||
*
|
||||
* private static Clock clock = new SystemClock();
|
||||
*
|
||||
* public long getTime() {
|
||||
* return clock.currentTimeMillis();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // DoomsdayTest.java
|
||||
* @RunWith(JUnit4.class)
|
||||
* public class DoomsdayTest {
|
||||
*
|
||||
* @Rule
|
||||
* public InjectRule inject = new InjectRule();
|
||||
*
|
||||
* private final FakeClock clock = new FakeClock();
|
||||
*
|
||||
* @Before
|
||||
* public void before() {
|
||||
* inject.setStaticField(Doomsday.class, "clock", clock);
|
||||
* }
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
123
javatests/com/google/domain/registry/testing/IoSpyRule.java
Normal file
123
javatests/com/google/domain/registry/testing/IoSpyRule.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
javatests/com/google/domain/registry/testing/Providers.java
Normal file
36
javatests/com/google/domain/registry/testing/Providers.java
Normal 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;
|
||||
}};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
63
javatests/com/google/domain/registry/testing/SystemInfo.java
Normal file
63
javatests/com/google/domain/registry/testing/SystemInfo.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
63
javatests/com/google/domain/registry/testing/TestObject.java
Normal file
63
javatests/com/google/domain/registry/testing/TestObject.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
47
javatests/com/google/domain/registry/testing/UserInfo.java
Normal file
47
javatests/com/google/domain/registry/testing/UserInfo.java
Normal 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() {}
|
||||
}
|
|
@ -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
|
|
|
@ -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
|
35
javatests/com/google/domain/registry/testing/mapreduce/BUILD
Normal file
35
javatests/com/google/domain/registry/testing/mapreduce/BUILD
Normal 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"],
|
||||
)
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
20
javatests/com/google/domain/registry/testing/sftp/BUILD
Normal file
20
javatests/com/google/domain/registry/testing/sftp/BUILD
Normal 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",
|
||||
],
|
||||
)
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue