mirror of
https://github.com/google/nomulus
synced 2026-01-18 03:33:04 +00:00
Compare commits
30 Commits
nomulus-20
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7387e975b | ||
|
|
5c6667507b | ||
|
|
c187c92ae4 | ||
|
|
22ca4e3f2b | ||
|
|
f27136458a | ||
|
|
d8e647316e | ||
|
|
d6e0a7b979 | ||
|
|
5725eb95e0 | ||
|
|
aa12998276 | ||
|
|
d415416bc5 | ||
|
|
3a1068f313 | ||
|
|
69e5d40f04 | ||
|
|
64f6cd9af4 | ||
|
|
40184689ca | ||
|
|
826ad85d20 | ||
|
|
2b47bc9b0a | ||
|
|
9555dca8c6 | ||
|
|
49484c06d3 | ||
|
|
81d222e7d6 | ||
|
|
7e9d4c27d1 | ||
|
|
f9c22ff1c5 | ||
|
|
2562d582f3 | ||
|
|
6f0bc1ded9 | ||
|
|
db9fc3271d | ||
|
|
84491fde70 | ||
|
|
0519e2ffcf | ||
|
|
85f75494ab | ||
|
|
cbba91558a | ||
|
|
c24f09febc | ||
|
|
fd51035f23 |
@@ -56,7 +56,7 @@ PROPERTIES_HEADER = """\
|
||||
# nom_build), run ./nom_build --help.
|
||||
#
|
||||
# DO NOT EDIT THIS FILE BY HAND
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.jvmargs=-Xmx4096m
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
"""
|
||||
@@ -117,28 +117,19 @@ PROPERTIES = [
|
||||
Property('dbUser', 'Database user name for use in connection'),
|
||||
Property('dbPassword', 'Database password for use in connection'),
|
||||
|
||||
Property('publish_repo',
|
||||
'Maven repository that hosts the Cloud SQL schema jar and the '
|
||||
'registry server test jars. Such jars are needed for '
|
||||
'server/schema integration tests. Please refer to <a '
|
||||
'href="./integration/README.md">integration project</a> for more '
|
||||
'information.'),
|
||||
Property('baseSchemaTag',
|
||||
'The nomulus version tag of the schema for use in the schema'
|
||||
'deployment integration test (:db:schemaIncrementalDeployTest)'),
|
||||
Property('schema_version',
|
||||
'The nomulus version tag of the schema for use in a database'
|
||||
'integration test.'),
|
||||
Property('nomulus_version',
|
||||
'The version of nomulus to test against in a database '
|
||||
'integration test.'),
|
||||
Property('dot_path',
|
||||
'The path to "dot", part of the graphviz package that converts '
|
||||
'a BEAM pipeline to image. Setting this property to empty string '
|
||||
'will disable image generation.',
|
||||
'/usr/bin/dot'),
|
||||
Property('pipeline',
|
||||
'The name of the Beam pipeline being staged.')
|
||||
'The name of the Beam pipeline being staged.'),
|
||||
Property('nomulus_env',
|
||||
'For use by scripts. Normally not set manually.'),
|
||||
Property('schema_env',
|
||||
'For use by scripts. Normally not set manually.'),
|
||||
Property('schemaTestArtifactsDir',
|
||||
'For use by scripts. Normally not set manually.')
|
||||
]
|
||||
|
||||
GRADLE_FLAGS = [
|
||||
|
||||
@@ -98,8 +98,8 @@ PRESUBMITS = {
|
||||
"File did not include the license header.",
|
||||
|
||||
# Files must end in a newline
|
||||
PresubmitCheck(r".*\n$", ("java", "js", "soy", "sql", "py", "sh", "gradle", "ts"),
|
||||
{"node_modules/"}, REQUIRED):
|
||||
PresubmitCheck(r".*\n$", ("java", "js", "soy", "sql", "py", "sh", "gradle", "ts", "xml"),
|
||||
{"node_modules/", ".idea"}, REQUIRED):
|
||||
"Source files must end in a newline.",
|
||||
|
||||
# System.(out|err).println should only appear in tools/ or load-testing/
|
||||
|
||||
@@ -646,23 +646,6 @@ artifacts {
|
||||
nomulus_test testUberJar
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
url project.publish_repo
|
||||
}
|
||||
}
|
||||
publications {
|
||||
nomulusTestsPublication(MavenPublication) {
|
||||
groupId 'google.registry'
|
||||
artifactId 'nomulus_test'
|
||||
version project.nomulus_version
|
||||
artifact nomulusFossJar
|
||||
artifact testUberJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task buildToolImage(dependsOn: nomulus, type: Exec) {
|
||||
commandLine 'docker', 'build', '-t', 'nomulus-tool', '.'
|
||||
}
|
||||
|
||||
@@ -50,8 +50,6 @@ import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.host.Host;
|
||||
@@ -73,7 +71,6 @@ import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import org.apache.beam.sdk.Pipeline;
|
||||
import org.apache.beam.sdk.PipelineResult;
|
||||
import org.apache.beam.sdk.coders.KvCoder;
|
||||
@@ -138,25 +135,21 @@ import org.joda.time.DateTime;
|
||||
* pairs of (contact/host repo ID: pending deposit) for all RDE pending deposits for further
|
||||
* processing.
|
||||
*
|
||||
* <h3>{@link Contact}</h3>
|
||||
*
|
||||
* We first join most recent contact histories, represented by (contact repo ID: contact history
|
||||
* revision ID) pairs, with referenced contacts, represented by (contact repo ID: pending deposit)
|
||||
* pairs, on the contact repo ID, to remove unreferenced contact histories. Contact resources are
|
||||
* then loaded from the remaining referenced contact histories, and marshalled into (pending
|
||||
* deposit: deposit fragment) pairs.
|
||||
*
|
||||
* <h3>{@link Host}</h3>
|
||||
*
|
||||
* Similar to {@link Contact}, we join the most recent host history with referenced hosts to find
|
||||
* most recent referenced hosts. For external hosts we do the same treatment as we did on contacts
|
||||
* and obtain the (pending deposit: deposit fragment) pairs. For subordinate hosts, we need to find
|
||||
* the superordinate domain in order to properly handle pending transfer in the deposit as well. So
|
||||
* we first find the superordinate domain repo ID from the host and join the (superordinate domain
|
||||
* repo ID: (subordinate host repo ID: (pending deposit: revision ID))) pair with the (domain repo
|
||||
* ID: revision ID) pair obtained from the domain history query in order to map the host at
|
||||
* watermark to the domain at watermark. We then proceed to create the (pending deposit: deposit
|
||||
* fragment) pair for subordinate hosts using the added domain information.
|
||||
* <p>We first join most recent host histories, represented by (host repo ID: host history revision
|
||||
* ID) pairs, with referenced hosts, represented by (host repo ID: pending deposit) pairs, on the
|
||||
* host repo ID, to remove unreferenced host histories. Host resources are then loaded from the
|
||||
* remaining referenced host histories, and marshalled into (pending deposit: deposit fragment)
|
||||
* pairs.
|
||||
*
|
||||
* <p>For subordinate hosts, we need to find the superordinate domain in order to properly handle
|
||||
* pending transfer in the deposit as well. So we first find the superordinate domain repo ID from
|
||||
* the host and join the (superordinate domain repo ID: (subordinate host repo ID: (pending deposit:
|
||||
* revision ID))) pair with the (domain repo ID: revision ID) pair obtained from the domain history
|
||||
* query in order to map the host at watermark to the domain at watermark. We then proceed to create
|
||||
* the (pending deposit: deposit fragment) pair for subordinate hosts using the added domain
|
||||
* information.
|
||||
*
|
||||
* <h2>Processing {@link DepositFragment}</h2>
|
||||
*
|
||||
@@ -184,10 +177,10 @@ public class RdePipeline implements Serializable {
|
||||
private final CloudTasksUtils cloudTasksUtils;
|
||||
private final RdeMarshaller marshaller;
|
||||
|
||||
// Registrars to be excluded from data escrow. Not including the sandbox-only OTE type so that
|
||||
// if sneaks into production we would get an extra signal.
|
||||
// Registrars to be excluded from data escrow (i.e. all registrar types that have a null IANA
|
||||
// identifier and thus would not be valid according to the RDE schema).
|
||||
private static final ImmutableSet<Type> IGNORED_REGISTRAR_TYPES =
|
||||
Sets.immutableEnumSet(Registrar.Type.MONITORING, Registrar.Type.TEST);
|
||||
Sets.immutableEnumSet(Registrar.Type.MONITORING, Registrar.Type.OTE, Registrar.Type.TEST);
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@@ -230,9 +223,6 @@ public class RdePipeline implements Serializable {
|
||||
PCollection<KV<String, Long>> domainHistories =
|
||||
getMostRecentHistoryEntries(pipeline, DomainHistory.class);
|
||||
|
||||
PCollection<KV<String, Long>> contactHistories =
|
||||
getMostRecentHistoryEntries(pipeline, ContactHistory.class);
|
||||
|
||||
PCollection<KV<String, Long>> hostHistories =
|
||||
getMostRecentHistoryEntries(pipeline, HostHistory.class);
|
||||
|
||||
@@ -241,10 +231,6 @@ public class RdePipeline implements Serializable {
|
||||
PCollection<KV<PendingDeposit, DepositFragment>> domainFragments =
|
||||
processedDomainHistories.get(DOMAIN_FRAGMENTS);
|
||||
|
||||
PCollection<KV<PendingDeposit, DepositFragment>> contactFragments =
|
||||
processContactHistories(
|
||||
processedDomainHistories.get(REFERENCED_CONTACTS), contactHistories);
|
||||
|
||||
PCollectionTuple processedHosts =
|
||||
processHostHistories(processedDomainHistories.get(REFERENCED_HOSTS), hostHistories);
|
||||
|
||||
@@ -256,7 +242,6 @@ public class RdePipeline implements Serializable {
|
||||
|
||||
return PCollectionList.of(registrarFragments)
|
||||
.and(domainFragments)
|
||||
.and(contactFragments)
|
||||
.and(externalHostFragments)
|
||||
.and(subordinateHostFragments)
|
||||
.apply(
|
||||
@@ -437,7 +422,6 @@ public class RdePipeline implements Serializable {
|
||||
private PCollectionTuple processDomainHistories(PCollection<KV<String, Long>> domainHistories) {
|
||||
Counter activeDomainCounter = Metrics.counter("RDE", "ActiveDomainBase");
|
||||
Counter domainFragmentCounter = Metrics.counter("RDE", "DomainFragment");
|
||||
Counter referencedContactCounter = Metrics.counter("RDE", "ReferencedContact");
|
||||
Counter referencedHostCounter = Metrics.counter("RDE", "ReferencedHost");
|
||||
return domainHistories.apply(
|
||||
"Map DomainHistory to DepositFragment " + "and emit referenced Contact and Host",
|
||||
@@ -463,19 +447,8 @@ public class RdePipeline implements Serializable {
|
||||
KV.of(
|
||||
pendingDeposit,
|
||||
marshaller.marshalDomain(domain, pendingDeposit.mode())));
|
||||
// Contacts and hosts are only deposited in RDE, not BRDA.
|
||||
// Hosts are only deposited in RDE, not BRDA.
|
||||
if (pendingDeposit.mode() == RdeMode.FULL) {
|
||||
HashSet<Serializable> contacts = new HashSet<>();
|
||||
domain.getAdminContact().ifPresent(c -> contacts.add(c.getKey()));
|
||||
domain.getTechContact().ifPresent(c -> contacts.add(c.getKey()));
|
||||
domain.getRegistrant().ifPresent(c -> contacts.add(c.getKey()));
|
||||
domain.getBillingContact().ifPresent(c -> contacts.add(c.getKey()));
|
||||
referencedContactCounter.inc(contacts.size());
|
||||
contacts.forEach(
|
||||
contactRepoId ->
|
||||
receiver
|
||||
.get(REFERENCED_CONTACTS)
|
||||
.output(KV.of((String) contactRepoId, pendingDeposit)));
|
||||
if (domain.getNsHosts() != null) {
|
||||
referencedHostCounter.inc(domain.getNsHosts().size());
|
||||
domain
|
||||
@@ -497,38 +470,6 @@ public class RdePipeline implements Serializable {
|
||||
DOMAIN_FRAGMENTS, TupleTagList.of(REFERENCED_CONTACTS).and(REFERENCED_HOSTS)));
|
||||
}
|
||||
|
||||
private PCollection<KV<PendingDeposit, DepositFragment>> processContactHistories(
|
||||
PCollection<KV<String, PendingDeposit>> referencedContacts,
|
||||
PCollection<KV<String, Long>> contactHistories) {
|
||||
Counter contactFragmentCounter = Metrics.counter("RDE", "ContactFragment");
|
||||
return removeUnreferencedResource(referencedContacts, contactHistories, Contact.class)
|
||||
.apply(
|
||||
"Map Contact to DepositFragment",
|
||||
FlatMapElements.into(
|
||||
kvs(
|
||||
TypeDescriptor.of(PendingDeposit.class),
|
||||
TypeDescriptor.of(DepositFragment.class)))
|
||||
.via(
|
||||
(KV<String, CoGbkResult> kv) -> {
|
||||
Contact contact =
|
||||
(Contact)
|
||||
loadResourceByHistoryEntryId(
|
||||
ContactHistory.class,
|
||||
kv.getKey(),
|
||||
kv.getValue().getAll(REVISION_ID));
|
||||
DepositFragment fragment = marshaller.marshalContact(contact);
|
||||
ImmutableSet<KV<PendingDeposit, DepositFragment>> fragments =
|
||||
Streams.stream(kv.getValue().getAll(PENDING_DEPOSIT))
|
||||
// The same contact could be used by multiple domains, therefore
|
||||
// matched to the same pending deposit multiple times.
|
||||
.distinct()
|
||||
.map(pendingDeposit -> KV.of(pendingDeposit, fragment))
|
||||
.collect(toImmutableSet());
|
||||
contactFragmentCounter.inc(fragments.size());
|
||||
return fragments;
|
||||
}));
|
||||
}
|
||||
|
||||
private PCollectionTuple processHostHistories(
|
||||
PCollection<KV<String, PendingDeposit>> referencedHosts,
|
||||
PCollection<KV<String, Long>> hostHistories) {
|
||||
|
||||
@@ -1462,6 +1462,12 @@ public final class RegistryConfig {
|
||||
return ImmutableSet.copyOf(config.mosapi.services);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("mosapiTldThreadCnt")
|
||||
public static int provideMosapiTldThreads(RegistryConfigSettings config) {
|
||||
return config.mosapi.tldThreadCnt;
|
||||
}
|
||||
|
||||
private static String formatComments(String text) {
|
||||
return Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(text).stream()
|
||||
.map(s -> "# " + s)
|
||||
|
||||
@@ -272,5 +272,6 @@ public class RegistryConfigSettings {
|
||||
public String entityType;
|
||||
public List<String> tlds;
|
||||
public List<String> services;
|
||||
public int tldThreadCnt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,4 +642,8 @@ mosapi:
|
||||
- "epp"
|
||||
- "dnssec"
|
||||
|
||||
# Provides a fixed thread pool for parallel TLD processing.
|
||||
# @see <a href="https://www.icann.org/mosapi-specification.pdf">
|
||||
# ICANN MoSAPI Specification, Section 12.3</a>
|
||||
tldThreadCnt: 4
|
||||
|
||||
|
||||
@@ -322,4 +322,15 @@
|
||||
<service>bsa</service>
|
||||
<schedule>23 8,20 * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/triggerMosApiServiceState]]></url>
|
||||
<name>triggerMosApiServiceState</name>
|
||||
<description>
|
||||
Fetches the service state from MosAPI and triggers the metrics status for all TLDs.
|
||||
</description>
|
||||
<!-- Runs every 5 minutes. -->
|
||||
<schedule>*/5 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
</entries>
|
||||
|
||||
@@ -194,13 +194,27 @@ public final class ResourceFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Check that the same values aren't being added and removed in an update command. */
|
||||
public static void checkSameValuesNotAddedAndRemoved(
|
||||
ImmutableSet<?> fieldsToAdd, ImmutableSet<?> fieldsToRemove)
|
||||
throws AddRemoveSameValueException {
|
||||
/**
|
||||
* Verifies the adds and removes on a resource.
|
||||
*
|
||||
* <p>This throws an exception in three different situations: if the same value is being both
|
||||
* added and removed, if a value is being added that is already present, or if a value is being
|
||||
* removed that isn't present.
|
||||
*/
|
||||
public static <T> void verifyAddsAndRemoves(
|
||||
ImmutableSet<T> existingFields, ImmutableSet<T> fieldsToAdd, ImmutableSet<T> fieldsToRemove)
|
||||
throws AddRemoveSameValueException,
|
||||
AddExistingValueException,
|
||||
RemoveNonexistentValueException {
|
||||
if (!intersection(fieldsToAdd, fieldsToRemove).isEmpty()) {
|
||||
throw new AddRemoveSameValueException();
|
||||
}
|
||||
if (!intersection(fieldsToAdd, existingFields).isEmpty()) {
|
||||
throw new AddExistingValueException();
|
||||
}
|
||||
if (intersection(fieldsToRemove, existingFields).size() != fieldsToRemove.size()) {
|
||||
throw new RemoveNonexistentValueException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Check that all {@link StatusValue} objects in a set are client-settable. */
|
||||
@@ -266,6 +280,20 @@ public final class ResourceFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Cannot add a value that is already present. */
|
||||
public static class AddExistingValueException extends ParameterValuePolicyErrorException {
|
||||
public AddExistingValueException() {
|
||||
super("Cannot add a value that is already present");
|
||||
}
|
||||
}
|
||||
|
||||
/** Cannot remove a value that does not exist. */
|
||||
public static class RemoveNonexistentValueException extends ParameterValuePolicyErrorException {
|
||||
public RemoveNonexistentValueException() {
|
||||
super("Cannot remove a value that does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
/** The specified status value cannot be set by clients. */
|
||||
public static class StatusNotClientSettableException extends ParameterValueRangeErrorException {
|
||||
public StatusNotClientSettableException(String statusValue) {
|
||||
|
||||
@@ -14,60 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Check;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CheckData.ContactCheck;
|
||||
import google.registry.model.eppoutput.CheckData.ContactCheckData;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An EPP flow that checks whether a contact can be provisioned.
|
||||
* An EPP flow that is meant to check whether a contact can be provisioned.
|
||||
*
|
||||
* <p>This flows can check the existence of multiple contacts simultaneously.
|
||||
*
|
||||
* @error {@link google.registry.flows.exceptions.TooManyResourceChecksException}
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_CHECK)
|
||||
public final class ContactCheckFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Clock clock;
|
||||
@Inject @Config("maxChecks") int maxChecks;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactCheckFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactCheckFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
ImmutableList<String> targetIds = ((Check) resourceCommand).getTargetIds();
|
||||
verifyTargetIdCount(targetIds, maxChecks);
|
||||
ImmutableSet<String> existingIds =
|
||||
ForeignKeyUtils.loadKeys(Contact.class, targetIds, clock.nowUtc()).keySet();
|
||||
ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String id : targetIds) {
|
||||
boolean unused = !existingIds.contains(id);
|
||||
checks.add(ContactCheck.create(unused, id, unused ? null : "In use"));
|
||||
}
|
||||
return responseBuilder.setResData(ContactCheckData.create(checks.build())).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,94 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
|
||||
import static google.registry.model.EppResourceUtils.createRepoId;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
|
||||
import google.registry.flows.exceptions.ResourceCreateContentionException;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Create;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CreateData.ContactCreateData;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that creates a new contact.
|
||||
* An EPP flow meant to create a new contact.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
* @error {@link ResourceAlreadyExistsForThisClientException}
|
||||
* @error {@link ResourceCreateContentionException}
|
||||
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
|
||||
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_CREATE)
|
||||
public final class ContactCreateFlow implements MutatingFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@Inject @Config("contactAndHostRoidSuffix") String roidSuffix;
|
||||
public final class ContactCreateFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactCreateFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
Create command = (Create) resourceCommand;
|
||||
DateTime now = tm().getTransactionTime();
|
||||
verifyResourceDoesNotExist(Contact.class, targetId, now, registrarId);
|
||||
Contact newContact =
|
||||
new Contact.Builder()
|
||||
.setContactId(targetId)
|
||||
.setAuthInfo(command.getAuthInfo())
|
||||
.setCreationRegistrarId(registrarId)
|
||||
.setPersistedCurrentSponsorRegistrarId(registrarId)
|
||||
.setRepoId(createRepoId(tm().allocateId(), roidSuffix))
|
||||
.setFaxNumber(command.getFax())
|
||||
.setVoiceNumber(command.getVoice())
|
||||
.setDisclose(command.getDisclose())
|
||||
.setEmailAddress(command.getEmail())
|
||||
.setInternationalizedPostalInfo(command.getInternationalizedPostalInfo())
|
||||
.setLocalizedPostalInfo(command.getLocalizedPostalInfo())
|
||||
.build();
|
||||
validateAsciiPostalInfo(newContact.getInternationalizedPostalInfo());
|
||||
validateContactAgainstPolicy(newContact);
|
||||
historyBuilder
|
||||
.setType(HistoryEntry.Type.CONTACT_CREATE)
|
||||
.setXmlBytes(null) // We don't want to store contact details in the history entry.
|
||||
.setContact(newContact);
|
||||
tm().insertAll(ImmutableSet.of(newContact, historyBuilder.build()));
|
||||
return responseBuilder
|
||||
.setResData(ContactCreateData.create(newContact.getContactId(), now))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,97 +14,20 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.DELETE_PROHIBITED_STATUSES;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.checkLinkedDomains;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
|
||||
import static google.registry.model.ResourceTransferUtils.handlePendingTransferOnDelete;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
|
||||
import static google.registry.model.transfer.TransferStatus.SERVER_CANCELLED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.reporting.HistoryEntry.Type;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that deletes a contact.
|
||||
* An EPP flow that is meant to delete a contact.
|
||||
*
|
||||
* <p>Contacts that are in use by any domain cannot be deleted. The flow may return immediately if a
|
||||
* quick smoke check determines that deletion is impossible due to an existing reference. However, a
|
||||
* successful delete will always be asynchronous, as all existing domains must be checked for
|
||||
* references to the host before the deletion is allowed to proceed. A poll message will be written
|
||||
* with the success or failure message when the process is complete.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link google.registry.flows.exceptions.ResourceToDeleteIsReferencedException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_DELETE)
|
||||
public final class ContactDeleteFlow implements MutatingFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject Trid trid;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
|
||||
public final class ContactDeleteFlow extends ContactsProhibitedFlow {
|
||||
@Inject
|
||||
ContactDeleteFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
checkLinkedDomains(targetId, now, Contact.class);
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyNoDisallowedStatuses(existingContact, ImmutableSet.of(StatusValue.PENDING_DELETE));
|
||||
if (!isSuperuser) {
|
||||
verifyNoDisallowedStatuses(existingContact, DELETE_PROHIBITED_STATUSES);
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
}
|
||||
// Handle pending transfers on contact deletion.
|
||||
Contact newContact =
|
||||
existingContact.getStatusValues().contains(StatusValue.PENDING_TRANSFER)
|
||||
? denyPendingTransfer(existingContact, SERVER_CANCELLED, now, registrarId)
|
||||
: existingContact;
|
||||
// Wipe out PII on contact deletion.
|
||||
newContact =
|
||||
newContact.asBuilder().wipeOut().setStatusValues(null).setDeletionTime(now).build();
|
||||
ContactHistory contactHistory =
|
||||
historyBuilder.setType(Type.CONTACT_DELETE).setContact(newContact).build();
|
||||
handlePendingTransferOnDelete(existingContact, newContact, now, contactHistory);
|
||||
tm().insert(contactHistory);
|
||||
tm().update(newContact);
|
||||
return responseBuilder.setResultFromCode(SUCCESS).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.model.contact.PostalInfo.Type.INTERNATIONALIZED;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Sets;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAddress;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Static utility functions for contact flows. */
|
||||
public class ContactFlowUtils {
|
||||
/** Check that an internationalized postal info has only ascii characters. */
|
||||
static void validateAsciiPostalInfo(@Nullable PostalInfo internationalized) throws EppException {
|
||||
if (internationalized != null) {
|
||||
Preconditions.checkState(INTERNATIONALIZED.equals(internationalized.getType()));
|
||||
ContactAddress address = internationalized.getAddress();
|
||||
Set<String> fields = Sets.newHashSet(
|
||||
internationalized.getName(),
|
||||
internationalized.getOrg(),
|
||||
address.getCity(),
|
||||
address.getCountryCode(),
|
||||
address.getState(),
|
||||
address.getZip());
|
||||
fields.addAll(address.getStreet());
|
||||
for (String field : fields) {
|
||||
if (field != null && !CharMatcher.ascii().matchesAllOf(field)) {
|
||||
throw new BadInternationalizedPostalInfoException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Check contact's state against server policy. */
|
||||
static void validateContactAgainstPolicy(Contact contact) throws EppException {
|
||||
if (contact.getDisclose() != null && !contact.getDisclose().getFlag()) {
|
||||
throw new DeclineContactDisclosureFieldDisallowedPolicyException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a poll message for the gaining client in a transfer. */
|
||||
static PollMessage createGainingTransferPollMessage(
|
||||
String targetId, TransferData transferData, DateTime now, HistoryEntryId contactHistoryId) {
|
||||
return new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId(transferData.getGainingRegistrarId())
|
||||
.setEventTime(transferData.getPendingTransferExpirationTime())
|
||||
.setMsg(transferData.getTransferStatus().getMessage())
|
||||
.setResponseData(
|
||||
ImmutableList.of(
|
||||
createTransferResponse(targetId, transferData),
|
||||
ContactPendingActionNotificationResponse.create(
|
||||
targetId,
|
||||
transferData.getTransferStatus().isApproved(),
|
||||
transferData.getTransferRequestTrid(),
|
||||
now)))
|
||||
.setContactHistoryId(contactHistoryId)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Create a poll message for the losing client in a transfer. */
|
||||
static PollMessage createLosingTransferPollMessage(
|
||||
String targetId, TransferData transferData, HistoryEntryId contactHistoryId) {
|
||||
return new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId(transferData.getLosingRegistrarId())
|
||||
.setEventTime(transferData.getPendingTransferExpirationTime())
|
||||
.setMsg(transferData.getTransferStatus().getMessage())
|
||||
.setResponseData(ImmutableList.of(createTransferResponse(targetId, transferData)))
|
||||
.setContactHistoryId(contactHistoryId)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Create a {@link ContactTransferResponse} off of the info in a {@link TransferData}. */
|
||||
static ContactTransferResponse createTransferResponse(
|
||||
String targetId, TransferData transferData) {
|
||||
return new ContactTransferResponse.Builder()
|
||||
.setContactId(targetId)
|
||||
.setGainingRegistrarId(transferData.getGainingRegistrarId())
|
||||
.setLosingRegistrarId(transferData.getLosingRegistrarId())
|
||||
.setPendingTransferExpirationTime(transferData.getPendingTransferExpirationTime())
|
||||
.setTransferRequestTime(transferData.getTransferRequestTime())
|
||||
.setTransferStatus(transferData.getTransferStatus())
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Declining contact disclosure is disallowed by server policy. */
|
||||
static class DeclineContactDisclosureFieldDisallowedPolicyException
|
||||
extends ParameterValuePolicyErrorException {
|
||||
public DeclineContactDisclosureFieldDisallowedPolicyException() {
|
||||
super("Declining contact disclosure is disallowed by server policy.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Internationalized postal infos can only contain ASCII characters. */
|
||||
static class BadInternationalizedPostalInfoException extends ParameterValueSyntaxErrorException {
|
||||
public BadInternationalizedPostalInfoException() {
|
||||
super("Internationalized postal infos can only contain ASCII characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,91 +14,20 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.model.EppResourceUtils.isLinked;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactInfoData;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that returns information about a contact.
|
||||
* An EPP flow that is meant to return information about a contact.
|
||||
*
|
||||
* <p>The response includes the contact's postal info, phone numbers, emails, the authInfo which can
|
||||
* be used to request a transfer and the details of the contact's most recent transfer if it has
|
||||
* ever been transferred. Any registrar can see any contact's information, but the authInfo is only
|
||||
* visible to the registrar that owns the contact or to a registrar that already supplied it.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_INFO)
|
||||
public final class ContactInfoFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Clock clock;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
|
||||
public final class ContactInfoFlow extends ContactsProhibitedFlow {
|
||||
@Inject
|
||||
ContactInfoFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
DateTime now = clock.nowUtc();
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
Contact contact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
if (!isSuperuser) {
|
||||
verifyResourceOwnership(registrarId, contact);
|
||||
}
|
||||
boolean includeAuthInfo =
|
||||
registrarId.equals(contact.getCurrentSponsorRegistrarId()) || authInfo.isPresent();
|
||||
ImmutableSet.Builder<StatusValue> statusValues = new ImmutableSet.Builder<>();
|
||||
statusValues.addAll(contact.getStatusValues());
|
||||
if (isLinked(contact.createVKey(), now)) {
|
||||
statusValues.add(StatusValue.LINKED);
|
||||
}
|
||||
return responseBuilder
|
||||
.setResData(
|
||||
ContactInfoData.newBuilder()
|
||||
.setContactId(contact.getContactId())
|
||||
.setRepoId(contact.getRepoId())
|
||||
.setStatusValues(statusValues.build())
|
||||
.setPostalInfos(contact.getPostalInfosAsList())
|
||||
.setVoiceNumber(contact.getVoiceNumber())
|
||||
.setFaxNumber(contact.getFaxNumber())
|
||||
.setEmailAddress(contact.getEmailAddress())
|
||||
.setCurrentSponsorRegistrarId(contact.getCurrentSponsorRegistrarId())
|
||||
.setCreationRegistrarId(contact.getCreationRegistrarId())
|
||||
.setCreationTime(contact.getCreationTime())
|
||||
.setLastEppUpdateRegistrarId(contact.getLastEppUpdateRegistrarId())
|
||||
.setLastEppUpdateTime(contact.getLastEppUpdateTime())
|
||||
.setLastTransferTime(contact.getLastTransferTime())
|
||||
.setAuthInfo(includeAuthInfo ? contact.getAuthInfo() : null)
|
||||
.setDisclose(contact.getDisclose())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,92 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyHasPendingTransfer;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.ResourceTransferUtils.approvePendingTransfer;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER_APPROVE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that approves a pending transfer on a contact.
|
||||
* An EPP flow that is meant to approve a pending transfer on a contact.
|
||||
*
|
||||
* <p>The "gaining" registrar requests a transfer from the "losing" (aka current) registrar. The
|
||||
* losing registrar has a "transfer" time period to respond (by default five days) after which the
|
||||
* transfer is automatically approved. Within that window, this flow allows the losing client to
|
||||
* explicitly approve the transfer request, which then becomes effective immediately.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_APPROVE)
|
||||
public final class ContactTransferApproveFlow implements MutatingFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactTransferApproveFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferApproveFlow() {}
|
||||
|
||||
/**
|
||||
* The logic in this flow, which handles client approvals, very closely parallels the logic in
|
||||
* {@link Contact#cloneProjectedAtTime} which handles implicit server approvals.
|
||||
*/
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyHasPendingTransfer(existingContact);
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
Contact newContact =
|
||||
approvePendingTransfer(existingContact, TransferStatus.CLIENT_APPROVED, now);
|
||||
ContactHistory contactHistory =
|
||||
historyBuilder.setType(CONTACT_TRANSFER_APPROVE).setContact(newContact).build();
|
||||
// Create a poll message for the gaining client.
|
||||
PollMessage gainingPollMessage =
|
||||
createGainingTransferPollMessage(
|
||||
targetId, newContact.getTransferData(), now, contactHistory.getHistoryEntryId());
|
||||
tm().insertAll(ImmutableSet.of(contactHistory, gainingPollMessage));
|
||||
tm().update(newContact);
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingContact.getTransferData().getServerApproveEntities());
|
||||
return responseBuilder
|
||||
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,88 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyHasPendingTransfer;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyTransferInitiator;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createLosingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER_CANCEL;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that cancels a pending transfer on a contact.
|
||||
* An EPP flow that is meant to cancel a pending transfer on a contact.
|
||||
*
|
||||
* <p>The "gaining" registrar requests a transfer from the "losing" (aka current) registrar. The
|
||||
* losing registrar has a "transfer" time period to respond (by default five days) after which the
|
||||
* transfer is automatically approved. Within that window, this flow allows the gaining client to
|
||||
* withdraw the transfer request.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||
* @error {@link google.registry.flows.exceptions.NotTransferInitiatorException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_CANCEL)
|
||||
public final class ContactTransferCancelFlow implements MutatingFlow {
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactTransferCancelFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferCancelFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyHasPendingTransfer(existingContact);
|
||||
verifyTransferInitiator(registrarId, existingContact);
|
||||
Contact newContact =
|
||||
denyPendingTransfer(existingContact, TransferStatus.CLIENT_CANCELLED, now, registrarId);
|
||||
ContactHistory contactHistory =
|
||||
historyBuilder.setType(CONTACT_TRANSFER_CANCEL).setContact(newContact).build();
|
||||
// Create a poll message for the losing client.
|
||||
PollMessage losingPollMessage =
|
||||
createLosingTransferPollMessage(
|
||||
targetId, newContact.getTransferData(), contactHistory.getHistoryEntryId());
|
||||
tm().insertAll(ImmutableSet.of(contactHistory, losingPollMessage));
|
||||
tm().update(newContact);
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingContact.getTransferData().getServerApproveEntities());
|
||||
return responseBuilder
|
||||
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,74 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.NoTransferHistoryToQueryException;
|
||||
import google.registry.flows.exceptions.NotAuthorizedToViewTransferException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* An EPP flow that queries a pending transfer on a contact.
|
||||
* An EPP flow that is meant to query a pending transfer on a contact.
|
||||
*
|
||||
* <p>The "gaining" registrar requests a transfer from the "losing" (aka current) registrar. The
|
||||
* losing registrar has a "transfer" time period to respond (by default five days) after which the
|
||||
* transfer is automatically approved. This flow can be used by the gaining or losing registrars (or
|
||||
* anyone with the correct authId) to see the status of a transfer, which may still be pending or
|
||||
* may have been approved, rejected, cancelled or implicitly approved by virtue of the transfer
|
||||
* period expiring.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.exceptions.NoTransferHistoryToQueryException}
|
||||
* @error {@link google.registry.flows.exceptions.NotAuthorizedToViewTransferException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_QUERY)
|
||||
public final class ContactTransferQueryFlow implements TransactionalFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject Clock clock;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactTransferQueryFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferQueryFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
Contact contact = loadAndVerifyExistence(Contact.class, targetId, clock.nowUtc());
|
||||
verifyOptionalAuthInfo(authInfo, contact);
|
||||
// Most of the fields on the transfer response are required, so there's no way to return valid
|
||||
// XML if the object has never been transferred (and hence the fields aren't populated).
|
||||
if (contact.getTransferData().getTransferStatus() == null) {
|
||||
throw new NoTransferHistoryToQueryException();
|
||||
}
|
||||
// Note that the authorization info on the command (if present) has already been verified. If
|
||||
// it's present, then the other checks are unnecessary.
|
||||
if (authInfo.isEmpty()
|
||||
&& !registrarId.equals(contact.getTransferData().getGainingRegistrarId())
|
||||
&& !registrarId.equals(contact.getTransferData().getLosingRegistrarId())) {
|
||||
throw new NotAuthorizedToViewTransferException();
|
||||
}
|
||||
return responseBuilder
|
||||
.setResData(createTransferResponse(targetId, contact.getTransferData()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,85 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyHasPendingTransfer;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER_REJECT;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that rejects a pending transfer on a contact.
|
||||
* An EPP flow that is meant to reject a pending transfer on a contact.
|
||||
*
|
||||
* <p>The "gaining" registrar requests a transfer from the "losing" (aka current) registrar. The
|
||||
* losing registrar has a "transfer" time period to respond (by default five days) after which the
|
||||
* transfer is automatically approved. Within that window, this flow allows the losing client to
|
||||
* reject the transfer request.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_REJECT)
|
||||
public final class ContactTransferRejectFlow implements MutatingFlow {
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactTransferRejectFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactTransferRejectFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyHasPendingTransfer(existingContact);
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
Contact newContact =
|
||||
denyPendingTransfer(existingContact, TransferStatus.CLIENT_REJECTED, now, registrarId);
|
||||
ContactHistory contactHistory =
|
||||
historyBuilder.setType(CONTACT_TRANSFER_REJECT).setContact(newContact).build();
|
||||
PollMessage gainingPollMessage =
|
||||
createGainingTransferPollMessage(
|
||||
targetId, newContact.getTransferData(), now, contactHistory.getHistoryEntryId());
|
||||
tm().insertAll(ImmutableSet.of(contactHistory, gainingPollMessage));
|
||||
tm().update(newContact);
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingContact.getTransferData().getServerApproveEntities());
|
||||
return responseBuilder
|
||||
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,162 +14,20 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.createHistoryEntryId;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoPresentForResourceTransfer;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createLosingTransferPollMessage;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER_REQUEST;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
||||
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.transfer.ContactTransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/**
|
||||
* An EPP flow that requests a transfer on a contact.
|
||||
* An EPP flow that is meant to request a transfer on a contact.
|
||||
*
|
||||
* <p>The "gaining" registrar requests a transfer from the "losing" (aka current) registrar. The
|
||||
* losing registrar has a "transfer" time period to respond (by default five days) after which the
|
||||
* transfer is automatically approved. Within that window, the transfer might be approved explicitly
|
||||
* by the losing registrar or rejected, and the gaining registrar can also cancel the transfer
|
||||
* request.
|
||||
*
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.exceptions.AlreadyPendingTransferException}
|
||||
* @error {@link google.registry.flows.exceptions.MissingTransferRequestAuthInfoException}
|
||||
* @error {@link google.registry.flows.exceptions.ObjectAlreadySponsoredException}
|
||||
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link ContactsProhibitedException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_TRANSFER_REQUEST)
|
||||
public final class ContactTransferRequestFlow implements MutatingFlow {
|
||||
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES =
|
||||
ImmutableSet.of(
|
||||
StatusValue.CLIENT_TRANSFER_PROHIBITED,
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_TRANSFER_PROHIBITED);
|
||||
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @RegistrarId String gainingClientId;
|
||||
@Inject @TargetId String targetId;
|
||||
|
||||
@Inject
|
||||
@Config("contactAutomaticTransferLength")
|
||||
Duration automaticTransferLength;
|
||||
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject Trid trid;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
|
||||
public final class ContactTransferRequestFlow extends ContactsProhibitedFlow {
|
||||
@Inject
|
||||
ContactTransferRequestFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(gainingClientId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyAuthInfoPresentForResourceTransfer(authInfo);
|
||||
verifyAuthInfo(authInfo.get(), existingContact);
|
||||
// Verify that the resource does not already have a pending transfer.
|
||||
if (TransferStatus.PENDING.equals(existingContact.getTransferData().getTransferStatus())) {
|
||||
throw new AlreadyPendingTransferException(targetId);
|
||||
}
|
||||
String losingClientId = existingContact.getCurrentSponsorRegistrarId();
|
||||
// Verify that this client doesn't already sponsor this resource.
|
||||
if (gainingClientId.equals(losingClientId)) {
|
||||
throw new ObjectAlreadySponsoredException();
|
||||
}
|
||||
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
|
||||
|
||||
DateTime transferExpirationTime = now.plus(automaticTransferLength);
|
||||
ContactTransferData serverApproveTransferData =
|
||||
new ContactTransferData.Builder()
|
||||
.setTransferRequestTime(now)
|
||||
.setTransferRequestTrid(trid)
|
||||
.setGainingRegistrarId(gainingClientId)
|
||||
.setLosingRegistrarId(losingClientId)
|
||||
.setPendingTransferExpirationTime(transferExpirationTime)
|
||||
.setTransferStatus(TransferStatus.SERVER_APPROVED)
|
||||
.build();
|
||||
HistoryEntryId contactHistoryId = createHistoryEntryId(existingContact);
|
||||
historyBuilder
|
||||
.setRevisionId(contactHistoryId.getRevisionId())
|
||||
.setType(CONTACT_TRANSFER_REQUEST);
|
||||
// If the transfer is server approved, this message will be sent to the losing registrar. */
|
||||
PollMessage serverApproveLosingPollMessage =
|
||||
createLosingTransferPollMessage(targetId, serverApproveTransferData, contactHistoryId);
|
||||
// If the transfer is server approved, this message will be sent to the gaining registrar. */
|
||||
PollMessage serverApproveGainingPollMessage =
|
||||
createGainingTransferPollMessage(
|
||||
targetId, serverApproveTransferData, now, contactHistoryId);
|
||||
ContactTransferData pendingTransferData =
|
||||
serverApproveTransferData
|
||||
.asBuilder()
|
||||
.setTransferStatus(TransferStatus.PENDING)
|
||||
.setServerApproveEntities(
|
||||
serverApproveGainingPollMessage.getContactRepoId(),
|
||||
contactHistoryId.getRevisionId(),
|
||||
ImmutableSet.of(
|
||||
serverApproveGainingPollMessage.createVKey(),
|
||||
serverApproveLosingPollMessage.createVKey()))
|
||||
.build();
|
||||
// When a transfer is requested, a poll message is created to notify the losing registrar.
|
||||
PollMessage requestPollMessage =
|
||||
createLosingTransferPollMessage(targetId, pendingTransferData, contactHistoryId)
|
||||
.asBuilder()
|
||||
.setEventTime(now) // Unlike the serverApprove messages, this applies immediately.
|
||||
.build();
|
||||
Contact newContact =
|
||||
existingContact
|
||||
.asBuilder()
|
||||
.setTransferData(pendingTransferData)
|
||||
.addStatusValue(StatusValue.PENDING_TRANSFER)
|
||||
.build();
|
||||
tm().update(newContact);
|
||||
tm().insertAll(
|
||||
ImmutableSet.of(
|
||||
historyBuilder.setContact(newContact).build(),
|
||||
requestPollMessage,
|
||||
serverApproveGainingPollMessage,
|
||||
serverApproveLosingPollMessage));
|
||||
return responseBuilder
|
||||
.setResultFromCode(SUCCESS_WITH_ACTION_PENDING)
|
||||
.setResData(createTransferResponse(targetId, newContact.getTransferData()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,158 +14,19 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.collect.Sets.union;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
|
||||
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_UPDATE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Update;
|
||||
import google.registry.model.contact.ContactCommand.Update.Change;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that updates a contact.
|
||||
* An EPP flow meant to update a contact.
|
||||
*
|
||||
* @error {@link ContactsProhibitedException}
|
||||
* @error {@link google.registry.flows.FlowUtils.NotLoggedInException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException}
|
||||
* @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException}
|
||||
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
|
||||
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
||||
*/
|
||||
@Deprecated
|
||||
@ReportingSpec(ActivityReportField.CONTACT_UPDATE)
|
||||
public final class ContactUpdateFlow implements MutatingFlow {
|
||||
|
||||
/**
|
||||
* Note that CLIENT_UPDATE_PROHIBITED is intentionally not in this list. This is because it
|
||||
* requires special checking, since you must be able to clear the status off the object with an
|
||||
* update.
|
||||
*/
|
||||
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_UPDATE_PROHIBITED);
|
||||
|
||||
@Inject ResourceCommand resourceCommand;
|
||||
@Inject ExtensionManager extensionManager;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject @RegistrarId String registrarId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject ContactHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
public final class ContactUpdateFlow extends ContactsProhibitedFlow {
|
||||
@Inject ContactUpdateFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
if (FeatureFlag.isActiveNow(MINIMUM_DATASET_CONTACTS_PROHIBITED)) {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
Update command = (Update) resourceCommand;
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
ImmutableSet<StatusValue> statusToRemove = command.getInnerRemove().getStatusValues();
|
||||
ImmutableSet<StatusValue> statusesToAdd = command.getInnerAdd().getStatusValues();
|
||||
if (!isSuperuser) { // The superuser can update any contact and set any status.
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
verifyAllStatusesAreClientSettable(union(statusesToAdd, statusToRemove));
|
||||
}
|
||||
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
|
||||
checkSameValuesNotAddedAndRemoved(statusesToAdd, statusToRemove);
|
||||
Contact.Builder builder = existingContact.asBuilder();
|
||||
Change change = command.getInnerChange();
|
||||
// The spec requires the following behaviors:
|
||||
// * If you update part of a postal info, the fields that you didn't update are unchanged.
|
||||
// * If you update one postal info but not the other, the other is deleted.
|
||||
// Therefore, if you want to preserve one postal info and update another you need to send the
|
||||
// update and also something that technically updates the preserved one, even if it only
|
||||
// "updates" it by setting just one field to the same value.
|
||||
PostalInfo internationalized = change.getInternationalizedPostalInfo();
|
||||
PostalInfo localized = change.getLocalizedPostalInfo();
|
||||
if (internationalized != null) {
|
||||
builder.overlayInternationalizedPostalInfo(internationalized);
|
||||
if (localized == null) {
|
||||
builder.setLocalizedPostalInfo(null);
|
||||
}
|
||||
}
|
||||
if (localized != null) {
|
||||
builder.overlayLocalizedPostalInfo(localized);
|
||||
if (internationalized == null) {
|
||||
builder.setInternationalizedPostalInfo(null);
|
||||
}
|
||||
}
|
||||
Contact newContact =
|
||||
builder
|
||||
.setLastEppUpdateTime(now)
|
||||
.setLastEppUpdateRegistrarId(registrarId)
|
||||
.setAuthInfo(preferFirst(change.getAuthInfo(), existingContact.getAuthInfo()))
|
||||
.setDisclose(preferFirst(change.getDisclose(), existingContact.getDisclose()))
|
||||
.setEmailAddress(preferFirst(change.getEmail(), existingContact.getEmailAddress()))
|
||||
.setFaxNumber(preferFirst(change.getFax(), existingContact.getFaxNumber()))
|
||||
.setVoiceNumber(preferFirst(change.getVoice(), existingContact.getVoiceNumber()))
|
||||
.addStatusValues(statusesToAdd)
|
||||
.removeStatusValues(statusToRemove)
|
||||
.build();
|
||||
// If the resource is marked with clientUpdateProhibited, and this update did not clear that
|
||||
// status, then the update must be disallowed (unless a superuser is requesting the change).
|
||||
if (!isSuperuser
|
||||
&& existingContact.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
|
||||
&& newContact.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) {
|
||||
throw new ResourceHasClientUpdateProhibitedException();
|
||||
}
|
||||
validateAsciiPostalInfo(newContact.getInternationalizedPostalInfo());
|
||||
validateContactAgainstPolicy(newContact);
|
||||
historyBuilder
|
||||
.setType(CONTACT_UPDATE)
|
||||
.setXmlBytes(null) // We don't want to store contact details in the history entry.
|
||||
.setContact(newContact);
|
||||
tm().insert(historyBuilder.build());
|
||||
tm().update(newContact);
|
||||
return responseBuilder.build();
|
||||
}
|
||||
|
||||
/** Return the first non-null param, or null if both are null. */
|
||||
@Nullable
|
||||
private static <T> T preferFirst(@Nullable T a, @Nullable T b) {
|
||||
return a != null ? a : b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
|
||||
/** Nomulus follows the Minimum Dataset Requirements, meaning it stores no contact information. */
|
||||
public abstract class ContactsProhibitedFlow implements Flow {
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
throw new ContactsProhibitedException();
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
@@ -298,11 +299,13 @@ public final class DomainCheckFlow implements TransactionalFlow {
|
||||
|
||||
boolean shouldUseTieredPricingPromotion =
|
||||
RegistryConfig.getTieredPricingPromotionRegistrarIds().contains(registrarId);
|
||||
ImmutableSet.Builder<CurrencyUnit> currenciesBuilder = new ImmutableSet.Builder<>();
|
||||
for (FeeCheckCommandExtensionItem feeCheckItem : feeCheck.getItems()) {
|
||||
for (String domainName : getDomainNamesToCheckForFee(feeCheckItem, domainNames.keySet())) {
|
||||
FeeCheckResponseExtensionItem.Builder<?> builder = feeCheckItem.createResponseBuilder();
|
||||
Optional<Domain> domain = Optional.ofNullable(domainObjs.get(domainName));
|
||||
Tld tld = Tld.get(domainNames.get(domainName).parent().toString());
|
||||
currenciesBuilder.add(tld.getCurrency());
|
||||
Optional<AllocationToken> token;
|
||||
try {
|
||||
// The precise token to use for this fee request may vary based on the domain or even the
|
||||
@@ -385,7 +388,8 @@ public final class DomainCheckFlow implements TransactionalFlow {
|
||||
responseItems.add(builder.setDomainNameIfSupported(domainName).build());
|
||||
}
|
||||
}
|
||||
return ImmutableList.of(feeCheck.createResponse(responseItems.build()));
|
||||
return ImmutableList.of(
|
||||
feeCheck.createResponse(responseItems.build(), currenciesBuilder.build()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -108,7 +108,8 @@ public final class DomainClaimsCheckFlow implements TransactionalFlow {
|
||||
verifyClaimsPeriodNotEnded(tld, now);
|
||||
}
|
||||
}
|
||||
Optional<String> claimKey = ClaimsListDao.get().getClaimKey(parsedDomain.parts().get(0));
|
||||
Optional<String> claimKey =
|
||||
ClaimsListDao.get(tldStr).getClaimKey(parsedDomain.parts().get(0));
|
||||
launchChecksBuilder.add(
|
||||
LaunchCheck.create(
|
||||
LaunchCheckName.create(claimKey.isPresent(), domainName), claimKey.orElse(null)));
|
||||
|
||||
@@ -280,7 +280,7 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
checkAllowedAccessToTld(registrarId, tld.getTldStr());
|
||||
checkHasBillingAccount(registrarId, tld.getTldStr());
|
||||
boolean isValidReservedCreate = isValidReservedCreate(domainName, allocationToken);
|
||||
ClaimsList claimsList = ClaimsListDao.get();
|
||||
ClaimsList claimsList = ClaimsListDao.get(tld.getTldStr());
|
||||
verifyIsGaOrSpecialCase(
|
||||
tld,
|
||||
claimsList,
|
||||
@@ -312,7 +312,8 @@ public final class DomainCreateFlow implements MutatingFlow {
|
||||
// at this point so that we can verify it before the "after validation" extension point.
|
||||
signedMarkId =
|
||||
tmchUtils
|
||||
.verifySignedMarks(launchCreate.get().getSignedMarks(), domainLabel, now)
|
||||
.verifySignedMarks(
|
||||
tld.getTldStr(), launchCreate.get().getSignedMarks(), domainLabel, now)
|
||||
.getId();
|
||||
}
|
||||
verifyNotBlockedByBsa(domainName, tld, now, allocationToken);
|
||||
|
||||
@@ -55,7 +55,7 @@ public final class DomainFlowTmchUtils {
|
||||
}
|
||||
|
||||
public SignedMark verifySignedMarks(
|
||||
ImmutableList<AbstractSignedMark> signedMarks, String domainLabel, DateTime now)
|
||||
String tld, ImmutableList<AbstractSignedMark> signedMarks, String domainLabel, DateTime now)
|
||||
throws EppException {
|
||||
if (signedMarks.size() > 1) {
|
||||
throw new TooManySignedMarksException();
|
||||
@@ -64,7 +64,7 @@ public final class DomainFlowTmchUtils {
|
||||
throw new SignedMarksMustBeEncodedException();
|
||||
}
|
||||
SignedMark signedMark =
|
||||
verifyEncodedSignedMark((EncodedSignedMark) signedMarks.get(0), now);
|
||||
verifyEncodedSignedMark(tld, (EncodedSignedMark) signedMarks.get(0), now);
|
||||
return verifySignedMarkValidForDomainLabel(signedMark, domainLabel);
|
||||
}
|
||||
|
||||
@@ -76,8 +76,9 @@ public final class DomainFlowTmchUtils {
|
||||
return signedMark;
|
||||
}
|
||||
|
||||
public SignedMark verifyEncodedSignedMark(EncodedSignedMark encodedSignedMark, DateTime now)
|
||||
throws EppException {
|
||||
// TODO(b/412715713): remove the tld parameter when RST completes.
|
||||
public SignedMark verifyEncodedSignedMark(
|
||||
String tld, EncodedSignedMark encodedSignedMark, DateTime now) throws EppException {
|
||||
if (!encodedSignedMark.getEncoding().equals("base64")) {
|
||||
throw new Base64RequiredForEncodedSignedMarksException();
|
||||
}
|
||||
@@ -95,7 +96,7 @@ public final class DomainFlowTmchUtils {
|
||||
throw new SignedMarkParsingErrorException();
|
||||
}
|
||||
|
||||
if (SignedMarkRevocationList.get().isSmdRevoked(signedMark.getId(), now)) {
|
||||
if (SignedMarkRevocationList.get(tld).isSmdRevoked(signedMark.getId(), now)) {
|
||||
throw new SignedMarkRevokedErrorException();
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ public class DomainFlowUtils {
|
||||
return domainName;
|
||||
}
|
||||
|
||||
private static void validateFirstLabel(String firstLabel) throws EppException {
|
||||
public static void validateFirstLabel(String firstLabel) throws EppException {
|
||||
if (firstLabel.length() > MAX_LABEL_SIZE) {
|
||||
throw new DomainLabelTooLongException();
|
||||
}
|
||||
@@ -976,23 +976,21 @@ public class DomainFlowUtils {
|
||||
throw new UrgentAttributeNotSupportedException();
|
||||
}
|
||||
// There must be at least one of add/rem/chg, and chg isn't actually supported.
|
||||
if (secDnsUpdate.getChange() != null) {
|
||||
if (secDnsUpdate.getChange().isPresent()) {
|
||||
// The only thing you can change is maxSigLife, and we don't support that at all.
|
||||
throw new MaxSigLifeChangeNotSupportedException();
|
||||
}
|
||||
Add add = secDnsUpdate.getAdd();
|
||||
Remove remove = secDnsUpdate.getRemove();
|
||||
if (add == null && remove == null) {
|
||||
Optional<Add> add = secDnsUpdate.getAdd();
|
||||
Optional<Remove> remove = secDnsUpdate.getRemove();
|
||||
if (add.isEmpty() && remove.isEmpty()) {
|
||||
throw new EmptySecDnsUpdateException();
|
||||
}
|
||||
if (remove != null && Boolean.FALSE.equals(remove.getAll())) {
|
||||
if (remove.isPresent() && Boolean.FALSE.equals(remove.get().getAll())) {
|
||||
throw new SecDnsAllUsageException(); // Explicit all=false is meaningless.
|
||||
}
|
||||
Set<DomainDsData> toAdd = (add == null) ? ImmutableSet.of() : add.getDsData();
|
||||
Set<DomainDsData> toAdd = add.map(Add::getDsData).orElse(ImmutableSet.of());
|
||||
Set<DomainDsData> toRemove =
|
||||
(remove == null)
|
||||
? ImmutableSet.of()
|
||||
: (remove.getAll() == null) ? remove.getDsData() : oldDsData;
|
||||
remove.map(r -> (r.getAll() == null) ? r.getDsData() : oldDsData).orElse(ImmutableSet.of());
|
||||
// RFC 5910 specifies that removes are processed before adds.
|
||||
return ImmutableSet.copyOf(union(difference(oldDsData, toRemove), toAdd));
|
||||
}
|
||||
@@ -1240,49 +1238,49 @@ public class DomainFlowUtils {
|
||||
}
|
||||
|
||||
/** Domain names can only contain a-z, 0-9, '.' and '-'. */
|
||||
static class BadDomainNameCharacterException extends ParameterValuePolicyErrorException {
|
||||
static class BadDomainNameCharacterException extends ParameterValueSyntaxErrorException {
|
||||
public BadDomainNameCharacterException() {
|
||||
super("Domain names can only contain a-z, 0-9, '.' and '-'");
|
||||
}
|
||||
}
|
||||
|
||||
/** Non-IDN domain names cannot contain hyphens in the third or fourth position. */
|
||||
static class DashesInThirdAndFourthException extends ParameterValuePolicyErrorException {
|
||||
static class DashesInThirdAndFourthException extends ParameterValueSyntaxErrorException {
|
||||
public DashesInThirdAndFourthException() {
|
||||
super("Non-IDN domain names cannot contain dashes in the third or fourth position");
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain labels cannot begin with a dash. */
|
||||
static class LeadingDashException extends ParameterValuePolicyErrorException {
|
||||
static class LeadingDashException extends ParameterValueSyntaxErrorException {
|
||||
public LeadingDashException() {
|
||||
super("Domain labels cannot begin with a dash");
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain labels cannot end with a dash. */
|
||||
static class TrailingDashException extends ParameterValuePolicyErrorException {
|
||||
static class TrailingDashException extends ParameterValueSyntaxErrorException {
|
||||
public TrailingDashException() {
|
||||
super("Domain labels cannot end with a dash");
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain labels cannot be longer than 63 characters. */
|
||||
static class DomainLabelTooLongException extends ParameterValuePolicyErrorException {
|
||||
static class DomainLabelTooLongException extends ParameterValueSyntaxErrorException {
|
||||
public DomainLabelTooLongException() {
|
||||
super("Domain labels cannot be longer than 63 characters");
|
||||
}
|
||||
}
|
||||
|
||||
/** No part of a domain name can be empty. */
|
||||
static class EmptyDomainNamePartException extends ParameterValuePolicyErrorException {
|
||||
static class EmptyDomainNamePartException extends ParameterValueSyntaxErrorException {
|
||||
public EmptyDomainNamePartException() {
|
||||
super("No part of a domain name can be empty");
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain name starts with xn-- but is not a valid IDN. */
|
||||
static class InvalidPunycodeException extends ParameterValuePolicyErrorException {
|
||||
static class InvalidPunycodeException extends ParameterValueSyntaxErrorException {
|
||||
public InvalidPunycodeException() {
|
||||
super("Domain name starts with xn-- but is not a valid IDN");
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import static com.google.common.collect.Sets.union;
|
||||
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
|
||||
import static google.registry.flows.FlowUtils.persistEntityChanges;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAddsAndRemoves;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
@@ -75,6 +75,8 @@ import google.registry.model.domain.fee.FeeUpdateCommandExtension;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
|
||||
import google.registry.model.domain.superuser.DomainUpdateSuperuserExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
@@ -245,12 +247,19 @@ public final class DomainUpdateFlow implements MutatingFlow {
|
||||
private Domain performUpdate(Update command, Domain domain, DateTime now) throws EppException {
|
||||
AddRemove add = command.getInnerAdd();
|
||||
AddRemove remove = command.getInnerRemove();
|
||||
checkSameValuesNotAddedAndRemoved(add.getNameservers(), remove.getNameservers());
|
||||
checkSameValuesNotAddedAndRemoved(add.getContacts(), remove.getContacts());
|
||||
checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues());
|
||||
Change change = command.getInnerChange();
|
||||
Optional<SecDnsUpdateExtension> secDnsUpdate =
|
||||
eppInput.getSingleExtension(SecDnsUpdateExtension.class);
|
||||
verifyAddsAndRemoves(domain.getNameservers(), add.getNameservers(), remove.getNameservers());
|
||||
verifyAddsAndRemoves(domain.getContacts(), add.getContacts(), remove.getContacts());
|
||||
verifyAddsAndRemoves(domain.getStatusValues(), add.getStatusValues(), remove.getStatusValues());
|
||||
if (secDnsUpdate.isPresent()) {
|
||||
SecDnsUpdateExtension ext = secDnsUpdate.get();
|
||||
verifyAddsAndRemoves(
|
||||
domain.getDsData(),
|
||||
ext.getAdd().map(Add::getDsData).orElse(ImmutableSet.of()),
|
||||
ext.getRemove().map(Remove::getDsData).orElse(ImmutableSet.of()));
|
||||
}
|
||||
Change change = command.getInnerChange();
|
||||
|
||||
// We have to verify no duplicate contacts _before_ constructing the domain because it is
|
||||
// illegal to construct a domain with duplicate contacts.
|
||||
|
||||
@@ -65,6 +65,7 @@ public final class HostCheckFlow implements TransactionalFlow {
|
||||
ForeignKeyUtils.loadKeys(Host.class, hostnames, clock.nowUtc()).keySet();
|
||||
ImmutableList.Builder<HostCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String hostname : hostnames) {
|
||||
HostFlowUtils.validateHostName(hostname);
|
||||
boolean unused = !existingIds.contains(hostname);
|
||||
checks.add(HostCheck.create(unused, hostname, unused ? null : "In use"));
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ public final class HostCreateFlow implements MutatingFlow {
|
||||
? new SubordinateHostMustHaveIpException()
|
||||
: new UnexpectedExternalHostIpException();
|
||||
}
|
||||
HostFlowUtils.validateInetAddresses(command.getInetAddresses());
|
||||
Host newHost =
|
||||
new Host.Builder()
|
||||
.setCreationRegistrarId(registrarId)
|
||||
|
||||
@@ -14,12 +14,16 @@
|
||||
|
||||
package google.registry.flows.host;
|
||||
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateFirstLabel;
|
||||
import static google.registry.model.EppResourceUtils.isActive;
|
||||
import static google.registry.model.tld.Tlds.findTldForName;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.AuthorizationErrorException;
|
||||
@@ -31,13 +35,17 @@ import google.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.util.Idn;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Static utility functions for host flows. */
|
||||
public class HostFlowUtils {
|
||||
|
||||
/** Validator for ASCII lowercase letters, digits, and "-_", allowing "." as a separator */
|
||||
private static final CharMatcher HOST_NAME_ALLOWED_CHARS =
|
||||
CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('0', '9').or(CharMatcher.anyOf("-._")));
|
||||
|
||||
/** Checks that a host name is valid. */
|
||||
public static InternetDomainName validateHostName(String name) throws EppException {
|
||||
checkArgumentNotNull(name, "Must specify host name to validate");
|
||||
@@ -49,10 +57,13 @@ public class HostFlowUtils {
|
||||
throw new HostNameNotLowerCaseException(hostNameLowerCase);
|
||||
}
|
||||
try {
|
||||
String hostNamePunyCoded = Idn.toASCII(name);
|
||||
String hostNamePunyCoded = canonicalizeHostname(name);
|
||||
if (!name.equals(hostNamePunyCoded)) {
|
||||
throw new HostNameNotPunyCodedException(hostNamePunyCoded);
|
||||
}
|
||||
if (!HOST_NAME_ALLOWED_CHARS.matchesAllOf(name)) {
|
||||
throw new BadHostNameCharacterException();
|
||||
}
|
||||
InternetDomainName hostName = InternetDomainName.from(name);
|
||||
if (!name.equals(hostName.toString())) {
|
||||
throw new HostNameNotNormalizedException(hostName.toString());
|
||||
@@ -71,6 +82,7 @@ public class HostFlowUtils {
|
||||
if (hostName.parts().size() < effectiveTld.parts().size() + 2) {
|
||||
throw new HostNameTooShallowException();
|
||||
}
|
||||
validateFirstLabel(hostName.parts().getFirst());
|
||||
return hostName;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidHostNameException();
|
||||
@@ -98,6 +110,24 @@ public class HostFlowUtils {
|
||||
return superordinateDomain;
|
||||
}
|
||||
|
||||
/** Makes sure that no provided IP addresses are local / loopback addresses. */
|
||||
public static void validateInetAddresses(ImmutableSet<InetAddress> inetAddresses)
|
||||
throws EppException {
|
||||
if (inetAddresses == null) {
|
||||
return;
|
||||
}
|
||||
if (inetAddresses.stream().anyMatch(InetAddress::isLoopbackAddress)) {
|
||||
throw new LoopbackIpNotValidForHostException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Loopback IPs are not valid for hosts. */
|
||||
static class LoopbackIpNotValidForHostException extends ParameterValuePolicyErrorException {
|
||||
public LoopbackIpNotValidForHostException() {
|
||||
super("Loopback IPs are not valid for hosts");
|
||||
}
|
||||
}
|
||||
|
||||
/** Superordinate domain for this hostname does not exist. */
|
||||
static class SuperordinateDomainDoesNotExistException extends ObjectDoesNotExistException {
|
||||
public SuperordinateDomainDoesNotExistException(String domainName) {
|
||||
@@ -180,4 +210,11 @@ public class HostFlowUtils {
|
||||
String.format("Host names must be in normalized format; expected %s", expectedHostName));
|
||||
}
|
||||
}
|
||||
|
||||
/** Host names can only contain a-z, 0-9, '.', '_', and '-'. */
|
||||
static class BadHostNameCharacterException extends ParameterValueSyntaxErrorException {
|
||||
public BadHostNameCharacterException() {
|
||||
super("Host names can only contain a-z, 0-9, '.', '_', and '-'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ import static google.registry.dns.DnsUtils.requestHostDnsRefresh;
|
||||
import static google.registry.dns.RefreshDnsOnHostRenameAction.PARAM_HOST_KEY;
|
||||
import static google.registry.dns.RefreshDnsOnHostRenameAction.QUEUE_HOST_RENAME;
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAddsAndRemoves;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
@@ -159,8 +159,11 @@ public final class HostUpdateFlow implements MutatingFlow {
|
||||
}
|
||||
AddRemove add = command.getInnerAdd();
|
||||
AddRemove remove = command.getInnerRemove();
|
||||
checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues());
|
||||
checkSameValuesNotAddedAndRemoved(add.getInetAddresses(), remove.getInetAddresses());
|
||||
verifyAddsAndRemoves(
|
||||
existingHost.getStatusValues(), add.getStatusValues(), remove.getStatusValues());
|
||||
verifyAddsAndRemoves(
|
||||
existingHost.getInetAddresses(), add.getInetAddresses(), remove.getInetAddresses());
|
||||
HostFlowUtils.validateInetAddresses(add.getInetAddresses());
|
||||
VKey<Domain> newSuperordinateDomainKey =
|
||||
newSuperordinateDomain.map(Domain::createVKey).orElse(null);
|
||||
// If the superordinateDomain field is changing, set the lastSuperordinateChange to now.
|
||||
|
||||
@@ -135,7 +135,6 @@ public class FlowPicker {
|
||||
return switch (((Poll) innerCommand).getPollOp()) {
|
||||
case ACK -> PollAckFlow.class;
|
||||
case REQUEST -> PollRequestFlow.class;
|
||||
default -> UnimplementedFlow.class;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package google.registry.model.domain.fee;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.model.eppinput.EppInput.CommandExtension;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
@@ -42,4 +44,11 @@ public interface FeeCheckCommandExtension<
|
||||
ImmutableList<C> getItems();
|
||||
|
||||
R createResponse(ImmutableList<? extends FeeCheckResponseExtensionItem> items);
|
||||
|
||||
default R createResponse(
|
||||
ImmutableList<? extends FeeCheckResponseExtensionItem> items,
|
||||
ImmutableSet<CurrencyUnit> currenciesSeen)
|
||||
throws EppException {
|
||||
return createResponse(items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.google.common.base.Ascii;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.fee.FeeCheckCommandExtensionItem;
|
||||
import jakarta.xml.bind.annotation.XmlAttribute;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
@@ -32,12 +31,13 @@ import org.joda.time.DateTime;
|
||||
* <pre>{@code
|
||||
* <fee:command name="renew" phase="sunrise" subphase="hello">
|
||||
* <fee:period unit="y">1</fee:period>
|
||||
* <fee:class>premium</fee:class>
|
||||
* <fee:date>2017-05-17T13:22:21.0Z</fee:date>
|
||||
* </fee:command>
|
||||
* }</pre>
|
||||
*
|
||||
* <p>The `feeClass` and `feeDate` attributes that are present in version 0.12 are removed from this
|
||||
* version.
|
||||
*/
|
||||
@XmlType(propOrder = {"period", "feeClass", "feeDate"})
|
||||
@XmlType(propOrder = {"period"})
|
||||
public class FeeCheckCommandExtensionItemStdV1 extends FeeCheckCommandExtensionItem {
|
||||
|
||||
/** The default validity period (if not specified) is 1 year for all operations. */
|
||||
@@ -50,12 +50,6 @@ public class FeeCheckCommandExtensionItemStdV1 extends FeeCheckCommandExtensionI
|
||||
|
||||
@XmlAttribute String subphase;
|
||||
|
||||
@XmlElement(name = "class")
|
||||
String feeClass;
|
||||
|
||||
@XmlElement(name = "date")
|
||||
DateTime feeDate;
|
||||
|
||||
/** Version 1.0 does not support domain name or currency in fee extension items. */
|
||||
@Override
|
||||
public boolean isDomainNameSupported() {
|
||||
@@ -107,6 +101,6 @@ public class FeeCheckCommandExtensionItemStdV1 extends FeeCheckCommandExtensionI
|
||||
|
||||
@Override
|
||||
public Optional<DateTime> getEffectiveDate() {
|
||||
return Optional.ofNullable(feeDate);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ package google.registry.model.domain.feestdv1;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.fee.FeeCheckCommandExtension;
|
||||
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
|
||||
@@ -51,13 +54,33 @@ public class FeeCheckCommandExtensionStdV1 extends ImmutableObject
|
||||
@Override
|
||||
public FeeCheckResponseExtensionStdV1 createResponse(
|
||||
ImmutableList<? extends FeeCheckResponseExtensionItem> items) {
|
||||
throw new UnsupportedOperationException("FeeCheckCommandExtensionStdV1 requires a currency");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeeCheckResponseExtensionStdV1 createResponse(
|
||||
ImmutableList<? extends FeeCheckResponseExtensionItem> items,
|
||||
ImmutableSet<CurrencyUnit> currenciesSeen)
|
||||
throws EppException {
|
||||
ImmutableList.Builder<FeeCheckResponseExtensionItemStdV1> builder =
|
||||
new ImmutableList.Builder<>();
|
||||
for (FeeCheckResponseExtensionItem item : items) {
|
||||
if (item instanceof FeeCheckResponseExtensionItemStdV1) {
|
||||
builder.add((FeeCheckResponseExtensionItemStdV1) item);
|
||||
if (item instanceof FeeCheckResponseExtensionItemStdV1 stdv1Item) {
|
||||
builder.add(stdv1Item);
|
||||
}
|
||||
}
|
||||
return FeeCheckResponseExtensionStdV1.create(currency, builder.build());
|
||||
if (currenciesSeen.size() > 1) {
|
||||
throw new MultipleCurrenciesCannotBeCheckedException();
|
||||
}
|
||||
return FeeCheckResponseExtensionStdV1.create(currenciesSeen.iterator().next(), builder.build());
|
||||
}
|
||||
|
||||
/** Domains across multiple currencies cannot be checked simultaneously. */
|
||||
public static class MultipleCurrenciesCannotBeCheckedException
|
||||
extends ParameterValuePolicyErrorException {
|
||||
public MultipleCurrenciesCannotBeCheckedException() {
|
||||
// The fee extension 1.0 only supports one currency shared across all results
|
||||
super("Domains across multiple currencies cannot be checked simultaneously");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,11 @@ import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.fee.Fee;
|
||||
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
|
||||
import jakarta.xml.bind.annotation.XmlAttribute;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.util.List;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** The version 1.0 response command entity for a domain check on a single resource. */
|
||||
@XmlType(propOrder = {"period", "fee", "feeClass", "effectiveDate", "notAfterDate"})
|
||||
@XmlType(propOrder = {"period", "fee"})
|
||||
public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
|
||||
|
||||
/** The command that was checked. */
|
||||
@@ -53,26 +51,6 @@ public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
|
||||
*/
|
||||
List<Fee> fee;
|
||||
|
||||
/**
|
||||
* The type of the fee.
|
||||
*
|
||||
* <p>We will use "premium" for fees on premium names, and omit the field otherwise.
|
||||
*/
|
||||
@XmlElement(name = "class")
|
||||
String feeClass;
|
||||
|
||||
/** The effective date that the check is to be performed on (if specified in the query). */
|
||||
@XmlElement(name = "date")
|
||||
DateTime effectiveDate;
|
||||
|
||||
/** The date after which the quoted fee is no longer valid (if applicable). */
|
||||
@XmlElement(name = "notAfter")
|
||||
DateTime notAfterDate;
|
||||
|
||||
public String getFeeClass() {
|
||||
return feeClass;
|
||||
}
|
||||
|
||||
/** Builder for {@link FeeCheckResponseExtensionItemCommandStdV1}. */
|
||||
public static class Builder extends Buildable.Builder<FeeCheckResponseExtensionItemCommandStdV1> {
|
||||
|
||||
@@ -96,24 +74,9 @@ public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setEffectiveDate(DateTime effectiveDate) {
|
||||
getInstance().effectiveDate = effectiveDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNotAfterDate(DateTime notAfterDate) {
|
||||
getInstance().notAfterDate = notAfterDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFee(List<Fee> fees) {
|
||||
getInstance().fee = forceEmptyToNull(ImmutableList.copyOf(fees));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setClass(String feeClass) {
|
||||
getInstance().feeClass = feeClass;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,20 +17,18 @@ package google.registry.model.domain.feestdv1;
|
||||
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.domain.DomainObjectSpec;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.fee.Fee;
|
||||
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
|
||||
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** The version 1.0 response for a domain check on a single resource. */
|
||||
@XmlType(propOrder = {"object", "command"})
|
||||
@XmlType(propOrder = {"objID", "feeClass", "command"})
|
||||
public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensionItem {
|
||||
|
||||
/** The domain that was checked. */
|
||||
DomainObjectSpec object;
|
||||
String objID;
|
||||
|
||||
/** The command that was checked. */
|
||||
FeeCheckResponseExtensionItemCommandStdV1 command;
|
||||
@@ -53,15 +51,6 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
|
||||
return super.getFees();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is not annotated for JAXB because this version of the extension doesn't support
|
||||
* "feeClass" and because the data comes off of the command object rather than a field.
|
||||
*/
|
||||
@Override
|
||||
public String getFeeClass() {
|
||||
return command.getFeeClass();
|
||||
}
|
||||
|
||||
/** Builder for {@link FeeCheckResponseExtensionItemStdV1}. */
|
||||
public static class Builder
|
||||
extends FeeCheckResponseExtensionItem.Builder<FeeCheckResponseExtensionItemStdV1> {
|
||||
@@ -91,13 +80,13 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
|
||||
|
||||
@Override
|
||||
public Builder setClass(String feeClass) {
|
||||
commandBuilder.setClass(feeClass);
|
||||
super.setClass(feeClass);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setDomainNameIfSupported(String name) {
|
||||
getInstance().object = new DomainObjectSpec(name);
|
||||
getInstance().objID = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -106,17 +95,5 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
|
||||
getInstance().command = commandBuilder.build();
|
||||
return super.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setEffectiveDateIfSupported(DateTime effectiveDate) {
|
||||
commandBuilder.setEffectiveDate(effectiveDate);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setNotAfterDateIfSupported(DateTime notAfterDate) {
|
||||
commandBuilder.setNotAfterDate(notAfterDate);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
@Access(AccessType.FIELD)
|
||||
public abstract class DomainDsDataBase extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@XmlTransient @Transient String domainRepoId;
|
||||
@XmlTransient @Transient @Insignificant String domainRepoId;
|
||||
|
||||
/** The identifier for this particular key in the domain. */
|
||||
@Transient int keyTag;
|
||||
|
||||
@@ -24,6 +24,7 @@ import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import jakarta.xml.bind.annotation.XmlTransient;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/** The EPP secDNS extension that may be present on domain update commands. */
|
||||
@@ -55,16 +56,16 @@ public class SecDnsUpdateExtension extends ImmutableObject implements CommandExt
|
||||
return urgent;
|
||||
}
|
||||
|
||||
public Remove getRemove() {
|
||||
return remove;
|
||||
public Optional<Remove> getRemove() {
|
||||
return Optional.ofNullable(remove);
|
||||
}
|
||||
|
||||
public Add getAdd() {
|
||||
return add;
|
||||
public Optional<Add> getAdd() {
|
||||
return Optional.ofNullable(add);
|
||||
}
|
||||
|
||||
public Change getChange() {
|
||||
return change;
|
||||
public Optional<Change> getChange() {
|
||||
return Optional.ofNullable(change);
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
|
||||
@@ -14,13 +14,17 @@
|
||||
|
||||
package google.registry.model.eppcommon;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import google.registry.xml.ValidationMode;
|
||||
import google.registry.xml.XmlException;
|
||||
import google.registry.xml.XmlTransformer;
|
||||
@@ -31,7 +35,7 @@ import java.io.ByteArrayOutputStream;
|
||||
public class EppXmlTransformer {
|
||||
|
||||
// Hardcoded XML schemas, ordered with respect to dependency.
|
||||
private static final ImmutableList<String> SCHEMAS =
|
||||
private static final ImmutableList<String> ALL_SCHEMAS =
|
||||
ImmutableList.of(
|
||||
"eppcom.xsd",
|
||||
"epp.xsd",
|
||||
@@ -54,11 +58,39 @@ public class EppXmlTransformer {
|
||||
"allocationToken-1.0.xsd",
|
||||
"bulkToken.xsd");
|
||||
|
||||
private static final XmlTransformer INPUT_TRANSFORMER =
|
||||
new XmlTransformer(SCHEMAS, EppInput.class);
|
||||
// XML schemas that should not be used in production (yet)
|
||||
private static final ImmutableSet<String> NON_PROD_SCHEMAS = ImmutableSet.of("fee-std-v1.xsd");
|
||||
|
||||
private static final XmlTransformer OUTPUT_TRANSFORMER =
|
||||
new XmlTransformer(SCHEMAS, EppOutput.class);
|
||||
// XML schemas that should only be used in production (for backcompat)
|
||||
private static final ImmutableSet<String> ONLY_PROD_SCHEMAS =
|
||||
ImmutableSet.of("fee06.xsd", "fee11.xsd", "fee12.xsd");
|
||||
|
||||
// TODO(gbrodman): make this final when we can actually remove the old fee extensions and aren't
|
||||
// relying on switching by environment
|
||||
@NonFinalForTesting
|
||||
private static XmlTransformer INPUT_TRANSFORMER =
|
||||
new XmlTransformer(getSchemas(), EppInput.class);
|
||||
|
||||
// TODO(gbrodman): make this final when we can actually remove the old fee extensions and aren't
|
||||
// relying on switching by environment
|
||||
@NonFinalForTesting
|
||||
private static XmlTransformer OUTPUT_TRANSFORMER =
|
||||
new XmlTransformer(getSchemas(), EppOutput.class);
|
||||
|
||||
@VisibleForTesting
|
||||
public static ImmutableList<String> getSchemas() {
|
||||
ImmutableSet<String> schemasToSkip =
|
||||
RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION)
|
||||
? NON_PROD_SCHEMAS
|
||||
: ONLY_PROD_SCHEMAS;
|
||||
return ALL_SCHEMAS.stream().filter(s -> !schemasToSkip.contains(s)).collect(toImmutableList());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void reloadTransformers() {
|
||||
INPUT_TRANSFORMER = new XmlTransformer(getSchemas(), EppInput.class);
|
||||
OUTPUT_TRANSFORMER = new XmlTransformer(getSchemas(), EppOutput.class);
|
||||
}
|
||||
|
||||
public static void validateOutput(String xml) throws XmlException {
|
||||
OUTPUT_TRANSFORMER.validate(xml);
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.model.eppcommon;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Maps.uniqueIndex;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.domain.fee06.FeeCheckCommandExtensionV06;
|
||||
@@ -33,6 +34,8 @@ import google.registry.model.domain.rgp.RgpUpdateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsCreateExtension;
|
||||
import google.registry.model.eppinput.EppInput.CommandExtension;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import jakarta.xml.bind.annotation.XmlSchema;
|
||||
import java.util.EnumSet;
|
||||
|
||||
@@ -43,35 +46,52 @@ public class ProtocolDefinition {
|
||||
public static final String LANGUAGE = "en";
|
||||
|
||||
public static final ImmutableSet<String> SUPPORTED_OBJECT_SERVICES =
|
||||
ImmutableSet.of(
|
||||
"urn:ietf:params:xml:ns:host-1.0",
|
||||
"urn:ietf:params:xml:ns:domain-1.0",
|
||||
"urn:ietf:params:xml:ns:contact-1.0");
|
||||
ImmutableSet.of("urn:ietf:params:xml:ns:host-1.0", "urn:ietf:params:xml:ns:domain-1.0");
|
||||
|
||||
/** Enums repesenting valid service extensions that are recognized by the server. */
|
||||
/** Enum representing which environments should have which service extensions enabled. */
|
||||
private enum ServiceExtensionVisibility {
|
||||
ALL,
|
||||
ONLY_IN_PRODUCTION,
|
||||
ONLY_IN_NON_PRODUCTION,
|
||||
NONE
|
||||
}
|
||||
|
||||
/** Enum representing valid service extensions that are recognized by the server. */
|
||||
public enum ServiceExtension {
|
||||
LAUNCH_EXTENSION_1_0(LaunchCreateExtension.class, null, true),
|
||||
REDEMPTION_GRACE_PERIOD_1_0(RgpUpdateExtension.class, null, true),
|
||||
SECURE_DNS_1_1(SecDnsCreateExtension.class, null, true),
|
||||
FEE_0_6(FeeCheckCommandExtensionV06.class, FeeCheckResponseExtensionV06.class, true),
|
||||
FEE_0_11(FeeCheckCommandExtensionV11.class, FeeCheckResponseExtensionV11.class, true),
|
||||
FEE_0_12(FeeCheckCommandExtensionV12.class, FeeCheckResponseExtensionV12.class, true),
|
||||
FEE_1_00(FeeCheckCommandExtensionStdV1.class, FeeCheckResponseExtensionStdV1.class, false),
|
||||
METADATA_1_0(MetadataExtension.class, null, false);
|
||||
LAUNCH_EXTENSION_1_0(LaunchCreateExtension.class, null, ServiceExtensionVisibility.ALL),
|
||||
REDEMPTION_GRACE_PERIOD_1_0(RgpUpdateExtension.class, null, ServiceExtensionVisibility.ALL),
|
||||
SECURE_DNS_1_1(SecDnsCreateExtension.class, null, ServiceExtensionVisibility.ALL),
|
||||
FEE_0_6(
|
||||
FeeCheckCommandExtensionV06.class,
|
||||
FeeCheckResponseExtensionV06.class,
|
||||
ServiceExtensionVisibility.ONLY_IN_PRODUCTION),
|
||||
FEE_0_11(
|
||||
FeeCheckCommandExtensionV11.class,
|
||||
FeeCheckResponseExtensionV11.class,
|
||||
ServiceExtensionVisibility.ONLY_IN_PRODUCTION),
|
||||
FEE_0_12(
|
||||
FeeCheckCommandExtensionV12.class,
|
||||
FeeCheckResponseExtensionV12.class,
|
||||
ServiceExtensionVisibility.ONLY_IN_PRODUCTION),
|
||||
FEE_1_00(
|
||||
FeeCheckCommandExtensionStdV1.class,
|
||||
FeeCheckResponseExtensionStdV1.class,
|
||||
ServiceExtensionVisibility.ONLY_IN_NON_PRODUCTION),
|
||||
METADATA_1_0(MetadataExtension.class, null, ServiceExtensionVisibility.NONE);
|
||||
|
||||
private final Class<? extends CommandExtension> commandExtensionClass;
|
||||
private final Class<? extends ResponseExtension> responseExtensionClass;
|
||||
private final String uri;
|
||||
private final boolean visible;
|
||||
private final ServiceExtensionVisibility visibility;
|
||||
|
||||
ServiceExtension(
|
||||
Class<? extends CommandExtension> commandExtensionClass,
|
||||
Class<? extends ResponseExtension> responseExtensionClass,
|
||||
boolean visible) {
|
||||
ServiceExtensionVisibility visibility) {
|
||||
this.commandExtensionClass = commandExtensionClass;
|
||||
this.responseExtensionClass = responseExtensionClass;
|
||||
this.uri = getCommandExtensionUri(commandExtensionClass);
|
||||
this.visible = visible;
|
||||
this.visibility = visibility;
|
||||
}
|
||||
|
||||
public Class<? extends CommandExtension> getCommandExtensionClass() {
|
||||
@@ -86,14 +106,20 @@ public class ProtocolDefinition {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public boolean getVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
/** Returns the namespace URI of the command extension class. */
|
||||
public static String getCommandExtensionUri(Class<? extends CommandExtension> clazz) {
|
||||
return clazz.getPackage().getAnnotation(XmlSchema.class).namespace();
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return switch (visibility) {
|
||||
case ALL -> true;
|
||||
case ONLY_IN_PRODUCTION -> RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION);
|
||||
case ONLY_IN_NON_PRODUCTION ->
|
||||
!RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION);
|
||||
case NONE -> false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,15 +136,25 @@ public class ProtocolDefinition {
|
||||
}
|
||||
|
||||
/** A set of all the visible extension URIs. */
|
||||
private static final ImmutableSet<String> visibleServiceExtensionUris =
|
||||
EnumSet.allOf(ServiceExtension.class)
|
||||
.stream()
|
||||
.filter(ServiceExtension::getVisible)
|
||||
.map(ServiceExtension::getUri)
|
||||
.collect(toImmutableSet());
|
||||
// TODO(gbrodman): make this final when we can actually remove the old fee extensions and aren't
|
||||
// relying on switching by environment
|
||||
@NonFinalForTesting private static ImmutableSet<String> visibleServiceExtensionUris;
|
||||
|
||||
static {
|
||||
reloadServiceExtensionUris();
|
||||
}
|
||||
|
||||
/** Return the set of all visible service extension URIs. */
|
||||
public static ImmutableSet<String> getVisibleServiceExtensionUris() {
|
||||
return visibleServiceExtensionUris;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void reloadServiceExtensionUris() {
|
||||
visibleServiceExtensionUris =
|
||||
EnumSet.allOf(ServiceExtension.class).stream()
|
||||
.filter(ServiceExtension::isVisible)
|
||||
.map(ServiceExtension::getUri)
|
||||
.collect(toImmutableSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import static google.registry.util.DateTimeUtils.isBeforeOrAt;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.tmch.RstTmchUtils;
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
@@ -71,6 +72,11 @@ public class SignedMarkRevocationList extends ImmutableObject {
|
||||
return CACHE.get();
|
||||
}
|
||||
|
||||
// TODO(b/412715713): remove the tld parameter when RST completes.
|
||||
public static SignedMarkRevocationList get(String tld) {
|
||||
return RstTmchUtils.getSmdrList(tld).orElseGet(SignedMarkRevocationList::get);
|
||||
}
|
||||
|
||||
/** Create a new {@link SignedMarkRevocationList} without saving it. */
|
||||
public static SignedMarkRevocationList create(
|
||||
DateTime creationTime, ImmutableMap<String, DateTime> revokes) {
|
||||
|
||||
@@ -1034,12 +1034,13 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
|
||||
return this;
|
||||
}
|
||||
|
||||
public static final Pattern ROID_SUFFIX_PATTERN = Pattern.compile("^[A-Z\\d_]{1,8}$");
|
||||
public static final Pattern ROID_SUFFIX_PATTERN = Pattern.compile("^[A-Z\\d]{1,8}$");
|
||||
|
||||
public Builder setRoidSuffix(String roidSuffix) {
|
||||
checkArgument(
|
||||
ROID_SUFFIX_PATTERN.matcher(roidSuffix).matches(),
|
||||
"ROID suffix must be in format %s",
|
||||
"ROID suffix %s must be in format %s",
|
||||
roidSuffix,
|
||||
ROID_SUFFIX_PATTERN.pattern());
|
||||
getInstance().roidSuffix = roidSuffix;
|
||||
return this;
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.CacheUtils;
|
||||
import google.registry.tmch.RstTmchUtils;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -72,6 +73,11 @@ public class ClaimsListDao {
|
||||
return CACHE.get(ClaimsListDao.class);
|
||||
}
|
||||
|
||||
// TODO(b/412715713): remove the tld parameter when RST completes.
|
||||
public static ClaimsList get(String tld) {
|
||||
return RstTmchUtils.getClaimsList(tld).orElseGet(ClaimsListDao::get);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recent revision of the {@link ClaimsList} in SQL or an empty list if it
|
||||
* doesn't exist.
|
||||
|
||||
@@ -62,6 +62,9 @@ import google.registry.module.ReadinessProbeAction.ReadinessProbeActionFrontend;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeActionPubApi;
|
||||
import google.registry.module.ReadinessProbeAction.ReadinessProbeConsoleAction;
|
||||
import google.registry.monitoring.whitebox.WhiteboxModule;
|
||||
import google.registry.mosapi.GetServiceStateAction;
|
||||
import google.registry.mosapi.TriggerServiceStateAction;
|
||||
import google.registry.mosapi.module.MosApiRequestModule;
|
||||
import google.registry.rdap.RdapAutnumAction;
|
||||
import google.registry.rdap.RdapDomainAction;
|
||||
import google.registry.rdap.RdapDomainSearchAction;
|
||||
@@ -151,6 +154,7 @@ import google.registry.ui.server.console.settings.SecurityAction;
|
||||
EppToolModule.class,
|
||||
IcannReportingModule.class,
|
||||
LoadTestModule.class,
|
||||
MosApiRequestModule.class,
|
||||
RdapModule.class,
|
||||
RdeModule.class,
|
||||
ReportingModule.class,
|
||||
@@ -232,6 +236,8 @@ interface RequestComponent {
|
||||
|
||||
GenerateZoneFilesAction generateZoneFilesAction();
|
||||
|
||||
GetServiceStateAction getServiceStateAction();
|
||||
|
||||
IcannReportingStagingAction icannReportingStagingAction();
|
||||
|
||||
IcannReportingUploadAction icannReportingUploadAction();
|
||||
@@ -334,6 +340,8 @@ interface RequestComponent {
|
||||
|
||||
TmchSmdrlAction tmchSmdrlAction();
|
||||
|
||||
TriggerServiceStateAction triggerServiceStateAction();
|
||||
|
||||
UpdateRegistrarRdapBaseUrlsAction updateRegistrarRdapBaseUrlsAction();
|
||||
|
||||
UpdateUserGroupAction updateUserGroupAction();
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.mosapi;
|
||||
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.gson.Gson;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.HttpException.ServiceUnavailableException;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/** An action that returns the current MoSAPI service state for a given TLD or all TLDs. */
|
||||
@Action(
|
||||
service = Action.Service.BACKEND,
|
||||
path = GetServiceStateAction.PATH,
|
||||
method = Action.Method.GET,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
public class GetServiceStateAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/mosapi/getServiceState";
|
||||
public static final String TLD_PARAM = "tld";
|
||||
|
||||
private final MosApiStateService stateService;
|
||||
private final Response response;
|
||||
private final Gson gson;
|
||||
private final Optional<String> tld;
|
||||
|
||||
@Inject
|
||||
public GetServiceStateAction(
|
||||
MosApiStateService stateService,
|
||||
Response response,
|
||||
Gson gson,
|
||||
@Parameter(TLD_PARAM) Optional<String> tld) {
|
||||
this.stateService = stateService;
|
||||
this.response = response;
|
||||
this.gson = gson;
|
||||
this.tld = tld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
response.setContentType(MediaType.JSON_UTF_8);
|
||||
try {
|
||||
if (tld.isPresent()) {
|
||||
response.setPayload(gson.toJson(stateService.getServiceStateSummary(tld.get())));
|
||||
} else {
|
||||
response.setPayload(gson.toJson(stateService.getAllServiceStateSummaries()));
|
||||
}
|
||||
} catch (MosApiException e) {
|
||||
throw new ServiceUnavailableException("Error fetching MoSAPI service state.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.mosapi.model;
|
||||
package google.registry.mosapi;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
/**
|
||||
* Represents the generic JSON error response from the MoSAPI service for a 400 Bad Request.
|
||||
@@ -20,4 +22,5 @@ package google.registry.mosapi.model;
|
||||
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification, Section
|
||||
* 8</a>
|
||||
*/
|
||||
public record MosApiErrorResponse(String resultCode, String message, String description) {}
|
||||
public record MosApiErrorResponse(
|
||||
@Expose String resultCode, @Expose String message, @Expose String description) {}
|
||||
@@ -17,7 +17,6 @@ package google.registry.mosapi;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import google.registry.mosapi.model.MosApiErrorResponse;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -42,6 +41,11 @@ public class MosApiException extends IOException {
|
||||
this.errorResponse = null;
|
||||
}
|
||||
|
||||
public MosApiException(String message) {
|
||||
super(message);
|
||||
this.errorResponse = null;
|
||||
}
|
||||
|
||||
public Optional<MosApiErrorResponse> getErrorResponse() {
|
||||
return Optional.ofNullable(errorResponse);
|
||||
}
|
||||
|
||||
34
core/src/main/java/google/registry/mosapi/MosApiMetrics.java
Normal file
34
core/src/main/java/google/registry/mosapi/MosApiMetrics.java
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.mosapi;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.mosapi.MosApiModels.TldServiceState;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
/** Metrics Exporter for MoSAPI. */
|
||||
public class MosApiMetrics {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@Inject
|
||||
public MosApiMetrics() {}
|
||||
|
||||
public void recordStates(List<TldServiceState> states) {
|
||||
// b/467541269: Logic to push status to Cloud Monitoring goes here
|
||||
logger.atInfo().log("MoSAPI record metrics logic will be implemented from here");
|
||||
}
|
||||
}
|
||||
122
core/src/main/java/google/registry/mosapi/MosApiModels.java
Normal file
122
core/src/main/java/google/registry/mosapi/MosApiModels.java
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.mosapi;
|
||||
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Data models for ICANN MoSAPI. */
|
||||
public final class MosApiModels {
|
||||
|
||||
private MosApiModels() {}
|
||||
|
||||
/**
|
||||
* A wrapper response containing the state summaries of all monitored services.
|
||||
*
|
||||
* <p>This corresponds to the collection of service statuses returned when monitoring the state of
|
||||
* a TLD
|
||||
*
|
||||
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
|
||||
* Section 5.1</a>
|
||||
*/
|
||||
public record AllServicesStateResponse(
|
||||
// A list of state summaries for each monitored service (e.g. DNS, RDDS, etc.)
|
||||
@Expose List<ServiceStateSummary> serviceStates) {
|
||||
|
||||
public AllServicesStateResponse {
|
||||
serviceStates = nullToEmptyImmutableCopy(serviceStates);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A summary of a service incident.
|
||||
*
|
||||
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
|
||||
* Section 5.1</a>
|
||||
*/
|
||||
public record IncidentSummary(
|
||||
@Expose String incidentID,
|
||||
@Expose long startTime,
|
||||
@Expose boolean falsePositive,
|
||||
@Expose String state,
|
||||
@Expose @Nullable Long endTime) {}
|
||||
|
||||
/**
|
||||
* A curated summary of the service state for a TLD.
|
||||
*
|
||||
* <p>This class aggregates the high-level status of a TLD and details of any active incidents
|
||||
* affecting specific services (like DNS or RDDS), based on the data structures defined in the
|
||||
* MoSAPI specification.
|
||||
*
|
||||
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
|
||||
* Section 5.1</a>
|
||||
*/
|
||||
public record ServiceStateSummary(
|
||||
@Expose String tld,
|
||||
@Expose String overallStatus,
|
||||
@Expose List<ServiceStatus> activeIncidents) {
|
||||
|
||||
public ServiceStateSummary {
|
||||
activeIncidents = nullToEmptyImmutableCopy(activeIncidents);
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents the status of a single monitored service. */
|
||||
public record ServiceStatus(
|
||||
/**
|
||||
* A JSON string that contains the status of the Service as seen from the monitoring system.
|
||||
* Possible values include "Up", "Down", "Disabled", "UP-inconclusive-no-data", etc.
|
||||
*/
|
||||
@Expose String status,
|
||||
|
||||
// A JSON number that contains the current percentage of the Emergency Threshold
|
||||
// of the Service. A value of "0" specifies that there are no Incidents
|
||||
// affecting the threshold.
|
||||
@Expose double emergencyThreshold,
|
||||
@Expose List<IncidentSummary> incidents) {
|
||||
|
||||
public ServiceStatus {
|
||||
incidents = nullToEmptyImmutableCopy(incidents);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the overall health of all monitored services for a TLD.
|
||||
*
|
||||
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
|
||||
* Section 5.1</a>
|
||||
*/
|
||||
public record TldServiceState(
|
||||
@Expose String tld,
|
||||
long lastUpdateApiDatabase,
|
||||
|
||||
// A JSON string that contains the status of the TLD as seen from the monitoring system
|
||||
@Expose String status,
|
||||
|
||||
// A JSON object containing detailed information for each potential monitored service (i.e.,
|
||||
// DNS,
|
||||
// RDDS, EPP, DNSSEC, RDAP).
|
||||
@Expose @SerializedName("testedServices") Map<String, ServiceStatus> serviceStatuses) {
|
||||
|
||||
public TldServiceState {
|
||||
serviceStatuses = nullToEmptyImmutableCopy(serviceStatuses);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.mosapi;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.mosapi.MosApiModels.AllServicesStateResponse;
|
||||
import google.registry.mosapi.MosApiModels.ServiceStateSummary;
|
||||
import google.registry.mosapi.MosApiModels.ServiceStatus;
|
||||
import google.registry.mosapi.MosApiModels.TldServiceState;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** A service that provides business logic for interacting with MoSAPI Service State. */
|
||||
public class MosApiStateService {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private final ServiceMonitoringClient serviceMonitoringClient;
|
||||
private final ExecutorService tldExecutor;
|
||||
|
||||
private final ImmutableSet<String> tlds;
|
||||
|
||||
private final MosApiMetrics mosApiMetrics;
|
||||
|
||||
private static final String DOWN_STATUS = "Down";
|
||||
private static final String FETCH_ERROR_STATUS = "ERROR";
|
||||
|
||||
@Inject
|
||||
public MosApiStateService(
|
||||
ServiceMonitoringClient serviceMonitoringClient,
|
||||
MosApiMetrics mosApiMetrics,
|
||||
@Config("mosapiTlds") ImmutableSet<String> tlds,
|
||||
@Named("mosapiTldExecutor") ExecutorService tldExecutor) {
|
||||
this.serviceMonitoringClient = serviceMonitoringClient;
|
||||
this.mosApiMetrics = mosApiMetrics;
|
||||
this.tlds = tlds;
|
||||
this.tldExecutor = tldExecutor;
|
||||
}
|
||||
|
||||
/** Fetches and transforms the service state for a given TLD into a summary. */
|
||||
public ServiceStateSummary getServiceStateSummary(String tld) throws MosApiException {
|
||||
TldServiceState rawState = serviceMonitoringClient.getTldServiceState(tld);
|
||||
return transformToSummary(rawState);
|
||||
}
|
||||
|
||||
/** Fetches and transforms the service state for all configured TLDs. */
|
||||
public AllServicesStateResponse getAllServiceStateSummaries() {
|
||||
ImmutableList<CompletableFuture<ServiceStateSummary>> futures =
|
||||
tlds.stream()
|
||||
.map(
|
||||
tld ->
|
||||
CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
try {
|
||||
return getServiceStateSummary(tld);
|
||||
} catch (MosApiException e) {
|
||||
logger.atWarning().withCause(e).log(
|
||||
"Failed to get service state for TLD %s.", tld);
|
||||
// we don't want to throw exception if fetch failed
|
||||
return new ServiceStateSummary(tld, FETCH_ERROR_STATUS, null);
|
||||
}
|
||||
},
|
||||
tldExecutor))
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
|
||||
ImmutableList<ServiceStateSummary> summaries =
|
||||
futures.stream()
|
||||
.map(CompletableFuture::join) // Waits for all tasks to complete
|
||||
.collect(toImmutableList());
|
||||
|
||||
return new AllServicesStateResponse(summaries);
|
||||
}
|
||||
|
||||
private ServiceStateSummary transformToSummary(TldServiceState rawState) {
|
||||
ImmutableList<ServiceStatus> activeIncidents = ImmutableList.of();
|
||||
if (DOWN_STATUS.equalsIgnoreCase(rawState.status())) {
|
||||
activeIncidents =
|
||||
rawState.serviceStatuses().entrySet().stream()
|
||||
.filter(
|
||||
entry -> {
|
||||
ServiceStatus serviceStatus = entry.getValue();
|
||||
return serviceStatus.incidents() != null
|
||||
&& !serviceStatus.incidents().isEmpty();
|
||||
})
|
||||
.map(
|
||||
entry ->
|
||||
new ServiceStatus(
|
||||
// key is the service name
|
||||
entry.getKey(),
|
||||
entry.getValue().emergencyThreshold(),
|
||||
entry.getValue().incidents()))
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
return new ServiceStateSummary(rawState.tld(), rawState.status(), activeIncidents);
|
||||
}
|
||||
|
||||
/** Triggers monitoring exposure for all configured TLDs. */
|
||||
public void triggerMetricsForAllServiceStateSummaries() {
|
||||
ImmutableList<CompletableFuture<TldServiceState>> futures =
|
||||
tlds.stream()
|
||||
.map(
|
||||
tld ->
|
||||
CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
try {
|
||||
return serviceMonitoringClient.getTldServiceState(tld);
|
||||
} catch (MosApiException e) {
|
||||
// Log the error but don't rethrow as RuntimeException
|
||||
logger.atWarning().withCause(e).log(
|
||||
"Failed to fetch state for TLD: %s", tld);
|
||||
return null; // Return null so the stream keeps moving
|
||||
}
|
||||
},
|
||||
tldExecutor))
|
||||
.collect(toImmutableList());
|
||||
|
||||
List<TldServiceState> allStates =
|
||||
futures.stream()
|
||||
.map(CompletableFuture::join)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!allStates.isEmpty()) {
|
||||
try {
|
||||
logger.atInfo().log("Triggering MoSAPI status to cloud monitoring for all TLDs.");
|
||||
mosApiMetrics.recordStates(allStates);
|
||||
} catch (Exception e) {
|
||||
logger.atSevere().withCause(e).log("Failed to submit MoSAPI metrics batch.");
|
||||
}
|
||||
} else {
|
||||
logger.atWarning().log("No successful TLD states fetched; skipping metrics push.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.mosapi;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import google.registry.mosapi.MosApiModels.TldServiceState;
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/** Facade for MoSAPI's service monitoring endpoints. */
|
||||
public class ServiceMonitoringClient {
|
||||
|
||||
private static final String MONITORING_STATE_ENDPOINT = "v2/monitoring/state";
|
||||
private final MosApiClient mosApiClient;
|
||||
private final Gson gson;
|
||||
|
||||
@Inject
|
||||
public ServiceMonitoringClient(MosApiClient mosApiClient, Gson gson) {
|
||||
this.mosApiClient = mosApiClient;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the current state of all monitored services for a given TLD.
|
||||
*
|
||||
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
|
||||
* Section 5.1</a>
|
||||
*/
|
||||
public TldServiceState getTldServiceState(String tld) throws MosApiException {
|
||||
try (Response response =
|
||||
mosApiClient.sendGetRequest(
|
||||
tld, MONITORING_STATE_ENDPOINT, Collections.emptyMap(), Collections.emptyMap())) {
|
||||
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody == null) {
|
||||
throw new MosApiException(
|
||||
String.format(
|
||||
"MoSAPI Service Monitoring API " + "returned an empty body with status: %d",
|
||||
response.code()));
|
||||
}
|
||||
String bodyString = responseBody.string();
|
||||
if (!response.isSuccessful()) {
|
||||
throw parseErrorResponse(response.code(), bodyString);
|
||||
}
|
||||
return gson.fromJson(bodyString, TldServiceState.class);
|
||||
} catch (IOException | JsonParseException e) {
|
||||
Throwables.throwIfInstanceOf(e, MosApiException.class);
|
||||
// Catch Gson's runtime exceptions (parsing errors) and wrap them
|
||||
throw new MosApiException("Failed to parse TLD service state response", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses an unsuccessful MoSAPI response into a domain-specific {@link MosApiException}. */
|
||||
private MosApiException parseErrorResponse(int statusCode, String bodyString) {
|
||||
try {
|
||||
MosApiErrorResponse error = gson.fromJson(bodyString, MosApiErrorResponse.class);
|
||||
return MosApiException.create(error);
|
||||
} catch (JsonParseException e) {
|
||||
return new MosApiException(
|
||||
String.format("MoSAPI json parsing error (%d): %s", statusCode, bodyString), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.mosapi;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.HttpException.InternalServerErrorException;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.request.auth.Auth;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
/**
|
||||
* An action that triggers Metrics action for the current MoSAPI service state result for all TLDs.
|
||||
*/
|
||||
@Action(
|
||||
service = Action.Service.BACKEND,
|
||||
path = TriggerServiceStateAction.PATH,
|
||||
method = Action.Method.GET,
|
||||
auth = Auth.AUTH_ADMIN)
|
||||
public class TriggerServiceStateAction implements Runnable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
public static final String PATH = "/_dr/task/triggerMosApiServiceState";
|
||||
private final MosApiStateService stateService;
|
||||
private final Response response;
|
||||
|
||||
@Inject
|
||||
public TriggerServiceStateAction(MosApiStateService stateService, Response response) {
|
||||
this.stateService = stateService;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
|
||||
try {
|
||||
logger.atInfo().log("Beginning to trigger MoSAPI metrics for all TLDs.");
|
||||
stateService.triggerMetricsForAllServiceStateSummaries();
|
||||
response.setStatus(200);
|
||||
response.setPayload("MoSAPI metrics triggered successfully for all TLDs.");
|
||||
} catch (Exception e) {
|
||||
logger.atSevere().withCause(e).log("Error triggering MoSAPI metrics.");
|
||||
throw new InternalServerErrorException("Failed to process MoSAPI metrics.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,8 @@ import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
@@ -184,4 +186,21 @@ public final class MosApiModule {
|
||||
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a fixed thread pool for parallel TLD processing.
|
||||
*
|
||||
* <p>Strictly bound to 4 threads to comply with MoSAPI session limits (4 concurrent sessions per
|
||||
* certificate). This is used by MosApiStateService to fetch data in parallel.
|
||||
*
|
||||
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
|
||||
* Section 12.3</a>
|
||||
*/
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("mosapiTldExecutor")
|
||||
static ExecutorService provideMosapiTldExecutor(
|
||||
@Config("mosapiTldThreadCnt") int threadPoolSize) {
|
||||
return Executors.newFixedThreadPool(threadPoolSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.mosapi.module;
|
||||
|
||||
import static google.registry.request.RequestParameters.extractOptionalParameter;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.request.Parameter;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Dagger module for MoSAPI requests. */
|
||||
@Module
|
||||
public final class MosApiRequestModule {
|
||||
@Provides
|
||||
@Parameter("tld")
|
||||
static Optional<String> provideTld(HttpServletRequest req) {
|
||||
return extractOptionalParameter(req, "tld");
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
@@ -118,12 +117,6 @@ public final class RdeMarshaller implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/** Turns {@link Contact} object into an XML fragment. */
|
||||
public DepositFragment marshalContact(Contact contact) {
|
||||
return marshalResource(
|
||||
RdeResourceType.CONTACT, contact, ContactToXjcConverter.convert(contact));
|
||||
}
|
||||
|
||||
/** Turns {@link Domain} object into an XML fragment. */
|
||||
public DepositFragment marshalDomain(Domain domain, RdeMode mode) {
|
||||
return marshalResource(
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.EnumSet;
|
||||
|
||||
/** Types of objects that get embedded in an escrow deposit. */
|
||||
public enum RdeResourceType {
|
||||
CONTACT("urn:ietf:params:xml:ns:rdeContact-1.0", EnumSet.of(FULL)),
|
||||
DOMAIN("urn:ietf:params:xml:ns:rdeDomain-1.0", EnumSet.of(FULL, THIN)),
|
||||
HOST("urn:ietf:params:xml:ns:rdeHost-1.0", EnumSet.of(FULL)),
|
||||
REGISTRAR("urn:ietf:params:xml:ns:rdeRegistrar-1.0", EnumSet.of(FULL, THIN)),
|
||||
|
||||
@@ -43,6 +43,15 @@ public enum IdnTableEnum {
|
||||
*/
|
||||
UNCONFUSABLE_LATIN("unconfusable_latin.txt"),
|
||||
|
||||
/**
|
||||
* ICANN LGR 2025 Latin, but with confusable characters removed.
|
||||
*
|
||||
* <p>This is based on <a
|
||||
* href="https://www.icann.org/sites/default/files/packages/lgr/lgr-second-level-latin-full-variant-script-24jan24-en.html">ICANN's
|
||||
* LGR table</a>, but is simpler.
|
||||
*/
|
||||
AUGMENTED_LATIN("augmented_latin.txt"),
|
||||
|
||||
/**
|
||||
* Japanese, as used on our existing TLD launches prior to 2023.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# URL: https://www.iana.org/domains/idn-tables/tables/google_latn_1.0.txt
|
||||
# URL: https://www.iana.org/domains/idn-tables/tables/google_latn_3.0.txt
|
||||
# Policy: https://www.registry.google/about/policies/domainabuse/
|
||||
U+002D # HYPHEN-MINUS
|
||||
U+0030 # DIGIT ZERO
|
||||
|
||||
120
core/src/main/java/google/registry/tmch/RstTmchUtils.java
Normal file
120
core/src/main/java/google/registry/tmch/RstTmchUtils.java
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.tmch;
|
||||
|
||||
import static com.google.common.base.Suppliers.memoize;
|
||||
import static com.google.common.io.Resources.getResource;
|
||||
import static com.google.common.io.Resources.readLines;
|
||||
import static google.registry.tmch.RstTmchUtils.RstEnvironment.OTE;
|
||||
import static google.registry.tmch.RstTmchUtils.RstEnvironment.PROD;
|
||||
import static google.registry.util.RegistryEnvironment.SANDBOX;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.smd.SignedMarkRevocationList;
|
||||
import google.registry.model.tmch.ClaimsList;
|
||||
import google.registry.util.RegistryEnvironment;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Utilities supporting TMCH-related RST testing in the Sandbox environment.
|
||||
*
|
||||
* <p>For logistic reasons we must conduct RST testing in the Sandbox environments. RST tests
|
||||
* require the use of special labels hosted on their website. To isolate these labels from regular
|
||||
* customers conducting onboarding tests, we manually download the test files as resources, and
|
||||
* serve them up only to RST TLDs.
|
||||
*/
|
||||
public class RstTmchUtils {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/**
|
||||
* The RST environments.
|
||||
*
|
||||
* <p>We conduct both OTE and PROD RST tests in Sandbox.
|
||||
*/
|
||||
enum RstEnvironment {
|
||||
OTE,
|
||||
PROD
|
||||
}
|
||||
|
||||
private static final ImmutableMap<RstEnvironment, Supplier<Optional<ClaimsList>>> CLAIMS_CACHE =
|
||||
ImmutableMap.of(
|
||||
OTE, memoize(() -> getClaimsList(OTE)), PROD, memoize(() -> getClaimsList(PROD)));
|
||||
|
||||
private static final ImmutableMap<RstEnvironment, Supplier<Optional<SignedMarkRevocationList>>>
|
||||
SMDRL_CACHE =
|
||||
ImmutableMap.of(
|
||||
OTE, memoize(() -> getSmdrList(OTE)), PROD, memoize(() -> getSmdrList(PROD)));
|
||||
|
||||
/** Returns appropriate test labels if {@code tld} is for RST testing; otherwise returns empty. */
|
||||
public static Optional<ClaimsList> getClaimsList(String tld) {
|
||||
return getRstEnvironment(tld).map(CLAIMS_CACHE::get).flatMap(Supplier::get);
|
||||
}
|
||||
|
||||
/** Returns appropriate test labels if {@code tld} is for RST testing; otherwise returns empty. */
|
||||
public static Optional<SignedMarkRevocationList> getSmdrList(String tld) {
|
||||
return getRstEnvironment(tld).map(SMDRL_CACHE::get).flatMap(Supplier::get);
|
||||
}
|
||||
|
||||
static Optional<RstEnvironment> getRstEnvironment(String tld) {
|
||||
if (!RegistryEnvironment.get().equals(SANDBOX)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (tld.startsWith("cc-rst-test-")) {
|
||||
return Optional.of(OTE);
|
||||
}
|
||||
if (tld.startsWith("zz--")) {
|
||||
return Optional.of(PROD);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<ClaimsList> getClaimsList(RstEnvironment rstEnvironment) {
|
||||
if (!RegistryEnvironment.get().equals(SANDBOX)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
String resourceName = rstEnvironment.name().toLowerCase(Locale.ROOT) + ".rst.dnl.csv";
|
||||
URL resource = getResource(RstTmchUtils.class, resourceName);
|
||||
try {
|
||||
return Optional.of(ClaimsListParser.parse(readLines(resource, UTF_8)));
|
||||
} catch (IOException e) {
|
||||
// Do not throw.
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Could not load Claims list %s for %s in Sandbox.", resourceName, rstEnvironment);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<SignedMarkRevocationList> getSmdrList(RstEnvironment rstEnvironment) {
|
||||
if (!RegistryEnvironment.get().equals(SANDBOX)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
String resourceName = rstEnvironment.name().toLowerCase(Locale.ROOT) + ".rst.smdrl.csv";
|
||||
URL resource = getResource(RstTmchUtils.class, resourceName);
|
||||
try {
|
||||
return Optional.of(SmdrlCsvParser.parse(readLines(resource, UTF_8)));
|
||||
} catch (IOException e) {
|
||||
// Do not throw.
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Could not load SMDR list %s for %s in Sandbox.", resourceName, rstEnvironment);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,12 @@ import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
|
||||
import static google.registry.util.RegistrarUtils.normalizeRegistrarName;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.certs.CertificateChecker;
|
||||
@@ -305,20 +305,16 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
|
||||
if (!allowedTlds.isEmpty()) {
|
||||
checkArgument(
|
||||
addAllowedTlds.isEmpty(), "Can't specify both --allowedTlds and --addAllowedTlds");
|
||||
ImmutableSet.Builder<String> allowedTldsBuilder = new ImmutableSet.Builder<>();
|
||||
for (String allowedTld : allowedTlds) {
|
||||
allowedTldsBuilder.add(canonicalizeHostname(allowedTld));
|
||||
}
|
||||
builder.setAllowedTlds(allowedTldsBuilder.build());
|
||||
builder.setAllowedTlds(
|
||||
allowedTlds.stream().map(Ascii::toLowerCase).collect(toImmutableSet()));
|
||||
}
|
||||
if (!addAllowedTlds.isEmpty()) {
|
||||
ImmutableSet.Builder<String> allowedTldsBuilder = new ImmutableSet.Builder<>();
|
||||
if (oldRegistrar != null) {
|
||||
allowedTldsBuilder.addAll(oldRegistrar.getAllowedTlds());
|
||||
}
|
||||
for (String allowedTld : addAllowedTlds) {
|
||||
allowedTldsBuilder.add(canonicalizeHostname(allowedTld));
|
||||
}
|
||||
allowedTldsBuilder.addAll(
|
||||
addAllowedTlds.stream().map(Ascii::toLowerCase).collect(toImmutableSet()));
|
||||
builder.setAllowedTlds(allowedTldsBuilder.build());
|
||||
}
|
||||
if (ipAllowList != null) {
|
||||
|
||||
@@ -38,7 +38,6 @@ import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.xml.XMLConstants;
|
||||
@@ -82,7 +81,7 @@ public class XmlTransformer {
|
||||
* @param schemaFilenames schema files, used only for validating, and relative to this package.
|
||||
* @param recognizedClasses the classes that can be used to marshal to and from
|
||||
*/
|
||||
public XmlTransformer(List<String> schemaFilenames, Class<?>... recognizedClasses) {
|
||||
public XmlTransformer(ImmutableList<String> schemaFilenames, Class<?>... recognizedClasses) {
|
||||
try {
|
||||
this.jaxbContext = JAXBContext.newInstance(recognizedClasses);
|
||||
this.schema = loadXmlSchemas(schemaFilenames);
|
||||
@@ -251,7 +250,7 @@ public class XmlTransformer {
|
||||
}
|
||||
|
||||
/** Creates a single {@link Schema} from multiple {@code .xsd} files. */
|
||||
public static Schema loadXmlSchemas(List<String> schemaFilenames) {
|
||||
public static Schema loadXmlSchemas(ImmutableList<String> schemaFilenames) {
|
||||
try (Closer closer = Closer.create()) {
|
||||
StreamSource[] sources = new StreamSource[schemaFilenames.size()];
|
||||
for (int i = 0; i < schemaFilenames.size(); ++i) {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<element name="renew" type="fee:transformCommandType" />
|
||||
<element name="renData" type="fee:transformResultType" />
|
||||
<element name="transfer" type="fee:transformCommandType" />
|
||||
<element name="trnData" type="fee:transferResultType" />
|
||||
<element name="trnData" type="fee:transformResultType" />
|
||||
<element name="update" type="fee:transformCommandType" />
|
||||
<element name="updData" type="fee:transformResultType" />
|
||||
<element name="delData" type="fee:transformResultType" />
|
||||
@@ -33,32 +33,24 @@
|
||||
<sequence>
|
||||
<element name="currency" type="fee:currencyType"
|
||||
minOccurs="0" />
|
||||
<element name="command" type="fee:commandCheckType"
|
||||
maxOccurs="unbounded" />
|
||||
<element name="command" type="fee:commandType"
|
||||
minOccurs="1" maxOccurs="unbounded" />
|
||||
</sequence>
|
||||
</complexType>
|
||||
|
||||
<complexType name="commandCheckType">
|
||||
<sequence>
|
||||
<element name="period"
|
||||
type="domain:periodType"
|
||||
minOccurs="0" />
|
||||
<element name="class"
|
||||
type="token"
|
||||
minOccurs="0" />
|
||||
<element name="date"
|
||||
type="dateTime"
|
||||
minOccurs="0" />
|
||||
</sequence>
|
||||
<attribute name="name" type="fee:commandTypeValue" />
|
||||
<attribute name="phase" type="token" />
|
||||
<attribute name="subphase" type="token" />
|
||||
<complexType name="objectIdentifierType">
|
||||
<simpleContent>
|
||||
<extension base="eppcom:labelType">
|
||||
<attribute name="element"
|
||||
type="NMTOKEN" default="name" />
|
||||
</extension>
|
||||
</simpleContent>
|
||||
</complexType>
|
||||
|
||||
<!-- server <check> result -->
|
||||
<complexType name="chkDataType">
|
||||
<sequence>
|
||||
<element name="currency" type="fee:currencyType" minOccurs="0"/>
|
||||
<element name="currency" type="fee:currencyType" />
|
||||
<element name="cd" type="fee:objectCDType"
|
||||
maxOccurs="unbounded" />
|
||||
</sequence>
|
||||
@@ -66,47 +58,13 @@
|
||||
|
||||
<complexType name="objectCDType">
|
||||
<sequence>
|
||||
<element name="object">
|
||||
<complexType>
|
||||
<sequence>
|
||||
<any namespace="##other" processContents="lax"/>
|
||||
</sequence>
|
||||
</complexType>
|
||||
</element>
|
||||
<element name="command"
|
||||
type="fee:commandCDType"
|
||||
maxOccurs="unbounded" />
|
||||
</sequence>
|
||||
</complexType>
|
||||
|
||||
<complexType name="commandCDType">
|
||||
<sequence>
|
||||
<element name="period"
|
||||
type="domain:periodType"
|
||||
minOccurs="0" maxOccurs="1" />
|
||||
<element name="fee"
|
||||
type="fee:feeType"
|
||||
<element name="objID" type="fee:objectIdentifierType" />
|
||||
<element name="class" type="token" minOccurs="0" />
|
||||
<element name="command" type="fee:commandDataType"
|
||||
minOccurs="0" maxOccurs="unbounded" />
|
||||
<element name="credit"
|
||||
type="fee:creditType"
|
||||
minOccurs="0" maxOccurs="unbounded" />
|
||||
<element name="class"
|
||||
type="token"
|
||||
minOccurs="0" />
|
||||
<element name="reason"
|
||||
type="token"
|
||||
minOccurs="0" />
|
||||
<element name="date"
|
||||
type="dateTime"
|
||||
minOccurs="0" />
|
||||
<element name="notAfter"
|
||||
type="dateTime"
|
||||
minOccurs="0" />
|
||||
<element name="reason" type="fee:reasonType" minOccurs="0" />
|
||||
</sequence>
|
||||
<attribute name="avail" type="boolean" default="1" />
|
||||
<attribute name="name" type="fee:commandTypeValue" />
|
||||
<attribute name="phase" type="token" />
|
||||
<attribute name="subphase" type="token" />
|
||||
</complexType>
|
||||
|
||||
<!-- general transform (create, renew, update, transfer) command-->
|
||||
@@ -121,32 +79,15 @@
|
||||
</sequence>
|
||||
</complexType>
|
||||
|
||||
<!-- general transform (create, renew, update, delete) result -->
|
||||
<!-- general transform (create, renew, update) result -->
|
||||
<complexType name="transformResultType">
|
||||
<sequence>
|
||||
<element name="currency" type="fee:currencyType" />
|
||||
<element name="fee" type="fee:feeType"
|
||||
minOccurs="0" maxOccurs="unbounded" />
|
||||
<element name="credit" type="fee:creditType"
|
||||
minOccurs="0" maxOccurs="unbounded" />
|
||||
<element name="balance" type="fee:balanceType"
|
||||
<element name="currency" type="fee:currencyType"
|
||||
minOccurs="0" />
|
||||
<element name="creditLimit" type="fee:creditLimitType"
|
||||
minOccurs="0" />
|
||||
</sequence>
|
||||
</complexType>
|
||||
|
||||
<!-- transfer result -->
|
||||
<complexType name="transferResultType">
|
||||
<sequence>
|
||||
<element name="currency" type="fee:currencyType" />
|
||||
|
||||
<!-- only used op="query" responses -->
|
||||
<element name="period" type="domain:periodType"
|
||||
minOccurs="0" />
|
||||
|
||||
<element name="fee" type="fee:feeType"
|
||||
maxOccurs="unbounded" />
|
||||
minOccurs="0" maxOccurs="unbounded" />
|
||||
<element name="credit" type="fee:creditType"
|
||||
minOccurs="0" maxOccurs="unbounded" />
|
||||
<element name="balance" type="fee:balanceType"
|
||||
@@ -163,10 +104,50 @@
|
||||
</restriction>
|
||||
</simpleType>
|
||||
|
||||
<simpleType name="commandTypeValue">
|
||||
<complexType name="commandType">
|
||||
<sequence>
|
||||
<element name="period" type="domain:periodType"
|
||||
minOccurs="0" maxOccurs="1" />
|
||||
</sequence>
|
||||
<attribute name="name" type="fee:commandEnum" use="required"/>
|
||||
<attribute name="customName" type="token"/>
|
||||
<attribute name="phase" type="token" />
|
||||
<attribute name="subphase" type="token" />
|
||||
</complexType>
|
||||
|
||||
<complexType name="commandDataType">
|
||||
<complexContent>
|
||||
<extension base="fee:commandType">
|
||||
<sequence>
|
||||
<element name="fee" type="fee:feeType"
|
||||
minOccurs="0" maxOccurs="unbounded" />
|
||||
<element name="credit" type="fee:creditType"
|
||||
minOccurs="0" maxOccurs="unbounded" />
|
||||
<element name="reason" type="fee:reasonType"
|
||||
minOccurs="0" />
|
||||
</sequence>
|
||||
<attribute name="standard" type="boolean" default="0" />
|
||||
</extension>
|
||||
</complexContent>
|
||||
</complexType>
|
||||
|
||||
<complexType name="reasonType">
|
||||
<simpleContent>
|
||||
<extension base="token">
|
||||
<attribute name="lang" type="language" default="en"/>
|
||||
</extension>
|
||||
</simpleContent>
|
||||
</complexType>
|
||||
|
||||
<simpleType name="commandEnum">
|
||||
<restriction base="token">
|
||||
<minLength value="3"/>
|
||||
<maxLength value="16"/>
|
||||
<enumeration value="create"/>
|
||||
<enumeration value="delete"/>
|
||||
<enumeration value="renew"/>
|
||||
<enumeration value="update"/>
|
||||
<enumeration value="transfer"/>
|
||||
<enumeration value="restore"/>
|
||||
<enumeration value="custom"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
|
||||
@@ -186,9 +167,10 @@
|
||||
<simpleContent>
|
||||
<extension base="fee:nonNegativeDecimal">
|
||||
<attribute name="description"/>
|
||||
<attribute name="lang" type="language" default="en"/>
|
||||
<attribute name="refundable" type="boolean" />
|
||||
<attribute name="grace-period" type="duration" />
|
||||
<attribute name="applied" default="immediate">
|
||||
<attribute name="applied">
|
||||
<simpleType>
|
||||
<restriction base="token">
|
||||
<enumeration value="immediate" />
|
||||
@@ -204,6 +186,7 @@
|
||||
<simpleContent>
|
||||
<extension base="fee:negativeDecimal">
|
||||
<attribute name="description"/>
|
||||
<attribute name="lang" type="language" default="en"/>
|
||||
</extension>
|
||||
</simpleContent>
|
||||
</complexType>
|
||||
|
||||
10
core/src/main/resources/google/registry/tmch/ote.rst.dnl.csv
Normal file
10
core/src/main/resources/google/registry/tmch/ote.rst.dnl.csv
Normal file
@@ -0,0 +1,10 @@
|
||||
1,2024-09-13T02:21:12.0Z
|
||||
DNL,lookup-key,insertion-datetime
|
||||
test---validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
test--validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
test-and-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
test-andvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
test-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
testand-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
testandvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
testvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
|
@@ -0,0 +1,7 @@
|
||||
1,2022-11-22T01:49:36.9Z
|
||||
smd-id,insertion-datetime
|
||||
0000001761385117375880-65535,2013-07-15T00:00:00.0Z
|
||||
0000001751501056761969-65535,2017-07-26T10:12:41.9Z
|
||||
000000541526299609231-65535,2018-05-14T17:52:23.7Z
|
||||
000000541602140609520-65535,2020-10-08T07:07:25.0Z
|
||||
000000541669081776937-65535,2022-11-22T01:49:36.9Z
|
||||
|
@@ -0,0 +1,10 @@
|
||||
1,2024-09-13T02:21:12.0Z
|
||||
DNL,lookup-key,insertion-datetime
|
||||
test---validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
test--validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
test-and-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
test-andvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
test-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
testand-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
testandvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
testvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
|
||||
|
@@ -0,0 +1,7 @@
|
||||
1,2022-11-22T01:49:36.9Z
|
||||
smd-id,insertion-datetime
|
||||
0000001761385117375880-65535,2013-07-15T00:00:00.0Z
|
||||
0000001751501056761969-65535,2017-07-26T10:12:41.9Z
|
||||
000000541526299609231-65535,2018-05-14T17:52:23.7Z
|
||||
000000541602140609520-65535,2020-10-08T07:07:25.0Z
|
||||
000000541669081776937-65535,2022-11-22T01:49:36.9Z
|
||||
|
@@ -335,7 +335,7 @@ class InvoicingPipelineTest {
|
||||
.build();
|
||||
persistResource(registrar);
|
||||
Tld test =
|
||||
newTld("test", "_TEST", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
|
||||
newTld("test", "TEST", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
|
||||
.asBuilder()
|
||||
.setInvoicingEnabled(true)
|
||||
.build();
|
||||
@@ -391,7 +391,7 @@ class InvoicingPipelineTest {
|
||||
// Test that comments are removed from the .sql file correctly
|
||||
assertThat(InvoicingPipeline.makeCloudSqlQuery("2017-10"))
|
||||
.isEqualTo(
|
||||
"""
|
||||
"""
|
||||
|
||||
SELECT b, r FROM BillingEvent b
|
||||
JOIN Registrar r ON b.clientId = r.registrarId
|
||||
@@ -449,13 +449,13 @@ AND cr.id IS NULL
|
||||
persistResource(registrar3);
|
||||
|
||||
Tld test =
|
||||
newTld("test", "_TEST", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
|
||||
newTld("test", "TEST", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
|
||||
.asBuilder()
|
||||
.setInvoicingEnabled(true)
|
||||
.build();
|
||||
persistResource(test);
|
||||
Tld hello =
|
||||
newTld("hello", "_HELLO", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
|
||||
newTld("hello", "HELLO", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
|
||||
.asBuilder()
|
||||
.setInvoicingEnabled(true)
|
||||
.build();
|
||||
|
||||
@@ -163,7 +163,7 @@ public class RegistryJpaReadTest {
|
||||
}
|
||||
|
||||
private void setupForJoinQuery() {
|
||||
Tld registry = newTld("com", "ABCD_APP");
|
||||
Tld registry = newTld("com", "ABCDAPP");
|
||||
Registrar registrar =
|
||||
makeRegistrar1()
|
||||
.asBuilder()
|
||||
|
||||
@@ -25,13 +25,11 @@ import static google.registry.model.rde.RdeMode.THIN;
|
||||
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrar1;
|
||||
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrar2;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.rde.RdeResourceType.CONTACT;
|
||||
import static google.registry.rde.RdeResourceType.DOMAIN;
|
||||
import static google.registry.rde.RdeResourceType.HOST;
|
||||
import static google.registry.rde.RdeResourceType.REGISTRAR;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.newDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static google.registry.testing.DatabaseHelper.persistEppResource;
|
||||
@@ -54,10 +52,6 @@ import google.registry.gcs.GcsUtils;
|
||||
import google.registry.keyring.api.PgpHelper;
|
||||
import google.registry.model.common.Cursor;
|
||||
import google.registry.model.common.Cursor.CursorType;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactBase;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
@@ -114,8 +108,6 @@ public class RdePipelineTest {
|
||||
|
||||
private static final String DOMAIN_NAME_PATTERN = "<rdeDomain:name>(.*)</rdeDomain:name>";
|
||||
|
||||
private static final String CONTACT_ID_PATTERN = "<rdeContact:id>(.*)</rdeContact:id>";
|
||||
|
||||
private static final String HOST_NAME_PATTERN = "<rdeHost:name>(.*)</rdeHost:name>";
|
||||
|
||||
// This is the default creation time for test data.
|
||||
@@ -139,7 +131,6 @@ public class RdePipelineTest {
|
||||
ImmutableList.of(
|
||||
DepositFragment.create(DOMAIN, "<rdeDomain:domain/>\n", ""),
|
||||
DepositFragment.create(REGISTRAR, "<rdeRegistrar:registrar/>\n", ""),
|
||||
DepositFragment.create(CONTACT, "<rdeContact:contact/>\n", ""),
|
||||
DepositFragment.create(HOST, "<rdeHost:host/>\n", ""));
|
||||
|
||||
private final GcsUtils gcsUtils = new GcsUtils(LocalStorageHelper.getOptions());
|
||||
@@ -165,21 +156,6 @@ public class RdePipelineTest {
|
||||
|
||||
private RdePipeline rdePipeline;
|
||||
|
||||
private ContactHistory persistContactHistory(ContactBase contact) {
|
||||
return persistResource(
|
||||
new ContactHistory.Builder()
|
||||
.setType(HistoryEntry.Type.HOST_CREATE)
|
||||
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
|
||||
.setModificationTime(clock.nowUtc())
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setTrid(Trid.create("ABC-123", "server-trid"))
|
||||
.setBySuperuser(false)
|
||||
.setReason("reason")
|
||||
.setRequestedByRegistrar(true)
|
||||
.setContact(contact)
|
||||
.build());
|
||||
}
|
||||
|
||||
private DomainHistory persistDomainHistory(DomainBase domain) {
|
||||
DomainTransactionRecord transactionRecord =
|
||||
new DomainTransactionRecord.Builder()
|
||||
@@ -254,20 +230,18 @@ public class RdePipelineTest {
|
||||
RdeRevision.saveRevision("soy", now, FULL, 0);
|
||||
});
|
||||
|
||||
// This contact is never referenced.
|
||||
persistContactHistory(persistActiveContact("contactX"));
|
||||
Contact contact1 = persistActiveContact("contact1234");
|
||||
persistContactHistory(contact1);
|
||||
Contact contact2 = persistActiveContact("contact456");
|
||||
persistContactHistory(contact2);
|
||||
|
||||
// This host is never referenced.
|
||||
persistHostHistory(persistActiveHost("ns0.domain.tld"));
|
||||
Host host1 = persistActiveHost("ns1.external.tld");
|
||||
persistHostHistory(host1);
|
||||
Domain helloDomain =
|
||||
persistEppResource(
|
||||
newDomain("hello.soy", contact1).asBuilder().addNameserver(host1.createVKey()).build());
|
||||
newDomain("hello.soy")
|
||||
.asBuilder()
|
||||
.addNameserver(host1.createVKey())
|
||||
.setRegistrant(Optional.empty())
|
||||
.setContacts(ImmutableSet.of())
|
||||
.build());
|
||||
persistDomainHistory(helloDomain);
|
||||
persistHostHistory(persistActiveHost("not-used-subordinate.hello.soy"));
|
||||
Host host2 = persistActiveHost("ns1.hello.soy");
|
||||
@@ -276,14 +250,15 @@ public class RdePipelineTest {
|
||||
// This domain has no registrant.
|
||||
Domain kittyDomain =
|
||||
persistEppResource(
|
||||
newDomain("kitty.fun", contact2)
|
||||
newDomain("kitty.fun")
|
||||
.asBuilder()
|
||||
.addNameservers(ImmutableSet.of(host1.createVKey(), host2.createVKey()))
|
||||
.setRegistrant(Optional.empty())
|
||||
.setContacts(ImmutableSet.of())
|
||||
.build());
|
||||
persistDomainHistory(kittyDomain);
|
||||
// Should not appear because the TLD is not included in a pending deposit.
|
||||
persistDomainHistory(persistEppResource(newDomain("lol.cat", contact1)));
|
||||
persistDomainHistory(persistEppResource(newDomain("lol.cat")));
|
||||
// To be deleted.
|
||||
Domain deletedDomain = persistActiveDomain("deleted.soy");
|
||||
persistDomainHistory(deletedDomain);
|
||||
@@ -293,8 +268,7 @@ public class RdePipelineTest {
|
||||
persistDomainHistory(deletedDomain.asBuilder().setDeletionTime(clock.nowUtc()).build());
|
||||
kittyDomain = kittyDomain.asBuilder().setDomainName("cat.fun").build();
|
||||
persistDomainHistory(kittyDomain);
|
||||
Contact contact3 = persistActiveContact("contact789");
|
||||
persistContactHistory(contact3);
|
||||
|
||||
// This is a subordinate domain in TLD .cat, which is not included in any pending deposit. But
|
||||
// it should still be included as a subordinate host in the pendign deposit for .soy.
|
||||
Host host3 = persistActiveHost("ns1.lol.cat");
|
||||
@@ -302,17 +276,8 @@ public class RdePipelineTest {
|
||||
persistDomainHistory(
|
||||
helloDomain
|
||||
.asBuilder()
|
||||
.removeContacts(
|
||||
helloDomain.getContacts().stream()
|
||||
.filter(dc -> dc.getType() == DesignatedContact.Type.ADMIN)
|
||||
.collect(toImmutableSet()))
|
||||
.addContacts(
|
||||
ImmutableSet.of(
|
||||
DesignatedContact.create(DesignatedContact.Type.ADMIN, contact3.createVKey())))
|
||||
.addNameserver(host3.createVKey())
|
||||
.build());
|
||||
// contact456 is renamed to contactABC.
|
||||
persistContactHistory(contact2.asBuilder().setContactId("contactABC").build());
|
||||
// ns1.hello.soy is renamed to ns2.hello.soy
|
||||
persistHostHistory(host2.asBuilder().setHostName("ns2.hello.soy").build());
|
||||
|
||||
@@ -320,18 +285,11 @@ public class RdePipelineTest {
|
||||
// resulting deposit fragments.
|
||||
clock.advanceBy(Duration.standardDays(2));
|
||||
persistDomainHistory(kittyDomain.asBuilder().setDeletionTime(clock.nowUtc()).build());
|
||||
Contact futureContact = persistActiveContact("future-contact");
|
||||
persistContactHistory(futureContact);
|
||||
Host futureHost = persistActiveHost("ns1.future.tld");
|
||||
persistHostHistory(futureHost);
|
||||
persistDomainHistory(
|
||||
persistEppResource(
|
||||
newDomain("future.soy", futureContact)
|
||||
.asBuilder()
|
||||
.setNameservers(futureHost.createVKey())
|
||||
.build()));
|
||||
// contactABC is renamed to contactXYZ.
|
||||
persistContactHistory(contact2.asBuilder().setContactId("contactXYZ").build());
|
||||
newDomain("future.soy").asBuilder().setNameservers(futureHost.createVKey()).build()));
|
||||
// ns2.hello.soy is renamed to ns3.hello.soy
|
||||
persistHostHistory(host2.asBuilder().setHostName("ns3.hello.soy").build());
|
||||
|
||||
@@ -390,11 +348,9 @@ public class RdePipelineTest {
|
||||
"""
|
||||
<rdeDomain:domain>
|
||||
<rdeDomain:name>cat.fun</rdeDomain:name>
|
||||
<rdeDomain:roid>15-FUN</rdeDomain:roid>
|
||||
<rdeDomain:roid>10-FUN</rdeDomain:roid>
|
||||
<rdeDomain:uName>cat.fun</rdeDomain:uName>
|
||||
<rdeDomain:status s="ok"/>
|
||||
<rdeDomain:contact type="admin">contact456</rdeDomain:contact>
|
||||
<rdeDomain:contact type="tech">contact456</rdeDomain:contact>
|
||||
<rdeDomain:ns>
|
||||
<domain:hostObj>ns1.external.tld</domain:hostObj>
|
||||
<domain:hostObj>ns1.hello.soy</domain:hostObj>
|
||||
@@ -407,14 +363,7 @@ public class RdePipelineTest {
|
||||
""");
|
||||
}
|
||||
if (kv.getKey().mode().equals(FULL)) {
|
||||
// Contact fragments for hello.soy.
|
||||
if ("soy".equals(kv.getKey().tld())) {
|
||||
assertThat(
|
||||
getFragmentForType(kv, CONTACT)
|
||||
.map(getXmlElement(CONTACT_ID_PATTERN))
|
||||
.collect(toImmutableSet()))
|
||||
.containsExactly("contact1234", "contact789");
|
||||
|
||||
// Host fragments for hello.soy.
|
||||
assertThat(
|
||||
getFragmentForType(kv, HOST)
|
||||
@@ -428,12 +377,9 @@ public class RdePipelineTest {
|
||||
"""
|
||||
<rdeDomain:domain>
|
||||
<rdeDomain:name>hello.soy</rdeDomain:name>
|
||||
<rdeDomain:roid>E-SOY</rdeDomain:roid>
|
||||
<rdeDomain:roid>8-SOY</rdeDomain:roid>
|
||||
<rdeDomain:uName>hello.soy</rdeDomain:uName>
|
||||
<rdeDomain:status s="ok"/>
|
||||
<rdeDomain:registrant>contact1234</rdeDomain:registrant>
|
||||
<rdeDomain:contact type="admin">contact789</rdeDomain:contact>
|
||||
<rdeDomain:contact type="tech">contact1234</rdeDomain:contact>
|
||||
<rdeDomain:ns>
|
||||
<domain:hostObj>ns1.external.tld</domain:hostObj>
|
||||
<domain:hostObj>ns1.lol.cat</domain:hostObj>
|
||||
@@ -445,13 +391,6 @@ public class RdePipelineTest {
|
||||
</rdeDomain:domain>\
|
||||
""");
|
||||
} else {
|
||||
// Contact fragments for cat.fun.
|
||||
assertThat(
|
||||
getFragmentForType(kv, CONTACT)
|
||||
.map(getXmlElement(CONTACT_ID_PATTERN))
|
||||
.collect(toImmutableSet()))
|
||||
.containsExactly("contactABC");
|
||||
|
||||
// Host fragments for cat.soy.
|
||||
assertThat(
|
||||
getFragmentForType(kv, HOST)
|
||||
@@ -460,22 +399,19 @@ public class RdePipelineTest {
|
||||
.containsExactly("ns1.external.tld", "ns2.hello.soy");
|
||||
}
|
||||
} else {
|
||||
// BRDA does not contain contact or hosts.
|
||||
// BRDA does not contain hosts.
|
||||
assertThat(
|
||||
Streams.stream(kv.getValue())
|
||||
.anyMatch(
|
||||
fragment ->
|
||||
fragment.type().equals(CONTACT)
|
||||
|| fragment.type().equals(HOST)))
|
||||
.anyMatch(fragment -> fragment.type().equals(HOST)))
|
||||
.isFalse();
|
||||
|
||||
// Domain fragments for hello.soy: Note that this contains no contact info.
|
||||
// Domain fragments for hello.soy.
|
||||
assertThat(domainFrags.stream().findFirst().get().xml().strip())
|
||||
.isEqualTo(
|
||||
"""
|
||||
<rdeDomain:domain>
|
||||
<rdeDomain:name>hello.soy</rdeDomain:name>
|
||||
<rdeDomain:roid>E-SOY</rdeDomain:roid>
|
||||
<rdeDomain:roid>8-SOY</rdeDomain:roid>
|
||||
<rdeDomain:uName>hello.soy</rdeDomain:uName>
|
||||
<rdeDomain:status s="ok"/>
|
||||
<rdeDomain:ns>
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.bsa;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.tldconfig.idn.IdnTableEnum.AUGMENTED_LATIN;
|
||||
import static google.registry.tldconfig.idn.IdnTableEnum.EXTENDED_LATIN;
|
||||
import static google.registry.tldconfig.idn.IdnTableEnum.JA;
|
||||
import static google.registry.tldconfig.idn.IdnTableEnum.UNCONFUSABLE_LATIN;
|
||||
@@ -43,6 +44,7 @@ public class IdnCheckerTest {
|
||||
Tld jaonly;
|
||||
Tld jandelatin;
|
||||
Tld strictlatin;
|
||||
Tld auglatin;
|
||||
IdnChecker idnChecker;
|
||||
|
||||
@BeforeEach
|
||||
@@ -50,6 +52,7 @@ public class IdnCheckerTest {
|
||||
jaonly = createTld("jaonly");
|
||||
jandelatin = createTld("jandelatin");
|
||||
strictlatin = createTld("strictlatin");
|
||||
auglatin = createTld("auglatin");
|
||||
|
||||
jaonly =
|
||||
persistResource(
|
||||
@@ -72,6 +75,13 @@ public class IdnCheckerTest {
|
||||
.setBsaEnrollStartTime(Optional.of(fakeClock.nowUtc()))
|
||||
.setIdnTables(ImmutableSet.of(UNCONFUSABLE_LATIN))
|
||||
.build());
|
||||
auglatin =
|
||||
persistResource(
|
||||
auglatin
|
||||
.asBuilder()
|
||||
.setBsaEnrollStartTime(Optional.of(fakeClock.nowUtc()))
|
||||
.setIdnTables(ImmutableSet.of(AUGMENTED_LATIN))
|
||||
.build());
|
||||
fakeClock.advanceOneMilli();
|
||||
idnChecker = new IdnChecker(fakeClock);
|
||||
}
|
||||
@@ -79,12 +89,13 @@ public class IdnCheckerTest {
|
||||
@Test
|
||||
void getAllValidIdns_allTlds() {
|
||||
assertThat(idnChecker.getAllValidIdns("all"))
|
||||
.containsExactly(EXTENDED_LATIN, JA, UNCONFUSABLE_LATIN);
|
||||
.containsExactly(EXTENDED_LATIN, JA, UNCONFUSABLE_LATIN, AUGMENTED_LATIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllValidIdns_notJa() {
|
||||
assertThat(idnChecker.getAllValidIdns("à")).containsExactly(EXTENDED_LATIN, UNCONFUSABLE_LATIN);
|
||||
assertThat(idnChecker.getAllValidIdns("à"))
|
||||
.containsExactly(EXTENDED_LATIN, UNCONFUSABLE_LATIN, AUGMENTED_LATIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -116,6 +127,7 @@ public class IdnCheckerTest {
|
||||
|
||||
@Test
|
||||
void getForbiddingTlds_success() {
|
||||
assertThat(idnChecker.getForbiddingTlds(ImmutableSet.of("JA"))).containsExactly(strictlatin);
|
||||
assertThat(idnChecker.getForbiddingTlds(ImmutableSet.of("JA")))
|
||||
.containsExactly(strictlatin, auglatin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows;
|
||||
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACK_MESSAGE;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_NO_MESSAGES;
|
||||
import static google.registry.testing.EppMetricSubject.assertThat;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Tests for contact lifecycle. */
|
||||
class EppLifecycleContactTest extends EppTestCase {
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
|
||||
|
||||
@Test
|
||||
void testContactLifecycle() throws Exception {
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
assertThatCommand("contact_create_sh8013.xml")
|
||||
.atTime("2000-06-01T00:00:00Z")
|
||||
.hasResponse(
|
||||
"contact_create_response_sh8013.xml",
|
||||
ImmutableMap.of("CRDATE", "2000-06-01T00:00:00Z"));
|
||||
assertThat(getRecordedEppMetric())
|
||||
.hasClientId("NewRegistrar")
|
||||
.and()
|
||||
.hasNoTld()
|
||||
.and()
|
||||
.hasCommandName("ContactCreate")
|
||||
.and()
|
||||
.hasStatus(SUCCESS);
|
||||
assertThatCommand("contact_info.xml")
|
||||
.atTime("2000-06-01T00:01:00Z")
|
||||
.hasResponse("contact_info_from_create_response.xml");
|
||||
assertThat(getRecordedEppMetric())
|
||||
.hasClientId("NewRegistrar")
|
||||
.and()
|
||||
.hasCommandName("ContactInfo")
|
||||
.and()
|
||||
.hasStatus(SUCCESS);
|
||||
assertThatCommand("contact_delete_sh8013.xml")
|
||||
.hasResponse("contact_delete_response_sh8013.xml");
|
||||
assertThat(getRecordedEppMetric())
|
||||
.hasClientId("NewRegistrar")
|
||||
.and()
|
||||
.hasCommandName("ContactDelete")
|
||||
.and()
|
||||
.hasStatus(SUCCESS);
|
||||
assertThatLogoutSucceeds();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testContactTransferPollMessage() throws Exception {
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
assertThatCommand("contact_create_sh8013.xml")
|
||||
.atTime("2000-06-01T00:00:00Z")
|
||||
.hasResponse(
|
||||
"contact_create_response_sh8013.xml",
|
||||
ImmutableMap.of("CRDATE", "2000-06-01T00:00:00Z"));
|
||||
assertThatLogoutSucceeds();
|
||||
|
||||
// Initiate a transfer of the newly created contact.
|
||||
assertThatLoginSucceeds("TheRegistrar", "password2");
|
||||
assertThatCommand("contact_transfer_request.xml")
|
||||
.atTime("2000-06-08T22:00:00Z")
|
||||
.hasResponse("contact_transfer_request_response_alternate.xml");
|
||||
assertThatLogoutSucceeds();
|
||||
|
||||
// Log back in with the losing registrar, read the poll message, and then ack it.
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
assertThatCommand("poll.xml")
|
||||
.atTime("2000-06-08T22:01:00Z")
|
||||
.hasResponse("poll_response_contact_transfer.xml");
|
||||
assertThat(getRecordedEppMetric())
|
||||
.hasClientId("NewRegistrar")
|
||||
.and()
|
||||
.hasCommandName("PollRequest")
|
||||
.and()
|
||||
.hasStatus(SUCCESS_WITH_ACK_MESSAGE);
|
||||
assertThatCommand("poll_ack.xml", ImmutableMap.of("ID", "6-2000"))
|
||||
.atTime("2000-06-08T22:02:00Z")
|
||||
.hasResponse("poll_ack_response_empty.xml");
|
||||
assertThat(getRecordedEppMetric())
|
||||
.hasClientId("NewRegistrar")
|
||||
.and()
|
||||
.hasCommandName("PollAck")
|
||||
.and()
|
||||
.hasStatus(SUCCESS_WITH_NO_MESSAGES);
|
||||
assertThatLogoutSucceeds();
|
||||
}
|
||||
}
|
||||
@@ -91,14 +91,6 @@ class EppLifecycleHostTest extends EppTestCase {
|
||||
createTld("example");
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
// Create the fakesite domain.
|
||||
assertThatCommand("contact_create_sh8013.xml")
|
||||
.atTime("2000-06-01T00:00:00Z")
|
||||
.hasResponse(
|
||||
"contact_create_response_sh8013.xml",
|
||||
ImmutableMap.of("CRDATE", "2000-06-01T00:00:00Z"));
|
||||
assertThatCommand("contact_create_jd1234.xml")
|
||||
.atTime("2000-06-01T00:01:00Z")
|
||||
.hasResponse("contact_create_response_jd1234.xml");
|
||||
assertThatCommand("domain_create_fakesite_no_nameservers.xml")
|
||||
.atTime("2000-06-01T00:04:00Z")
|
||||
.hasResponse(
|
||||
@@ -142,15 +134,6 @@ class EppLifecycleHostTest extends EppTestCase {
|
||||
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
|
||||
assertThatCommand("contact_create_sh8013.xml")
|
||||
.atTime("2000-06-01T00:00:00Z")
|
||||
.hasResponse(
|
||||
"contact_create_response_sh8013.xml",
|
||||
ImmutableMap.of("CRDATE", "2000-06-01T00:00:00Z"));
|
||||
assertThatCommand("contact_create_jd1234.xml")
|
||||
.atTime("2000-06-01T00:01:00Z")
|
||||
.hasResponse("contact_create_response_jd1234.xml");
|
||||
|
||||
// Create domain example.bar.foo.tld
|
||||
assertThatCommand(
|
||||
"domain_create_no_hosts_or_dsdata.xml",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.flows;
|
||||
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
import static org.joda.time.format.ISODateTimeFormat.dateTimeNoMillis;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -26,7 +25,7 @@ class EppLoggedOutTest extends EppTestCase {
|
||||
|
||||
@Test
|
||||
void testHello() throws Exception {
|
||||
DateTime now = DateTime.now(UTC);
|
||||
DateTime now = clock.nowUtc();
|
||||
assertThatCommand("hello.xml", null)
|
||||
.atTime(now)
|
||||
.hasResponse("greeting.xml", ImmutableMap.of("DATE", now.toString(dateTimeNoMillis())));
|
||||
|
||||
@@ -223,7 +223,7 @@ public class EppTestCase {
|
||||
return eppMetricBuilder.build();
|
||||
}
|
||||
|
||||
/** Create the two administrative contacts and two hosts. */
|
||||
/** Create the two hosts. */
|
||||
void createHosts() throws Exception {
|
||||
DateTime createTime = DateTime.parse("2000-06-01T00:00:00Z");
|
||||
assertThatCommand("host_create.xml", ImmutableMap.of("HOSTNAME", "ns1.example.external"))
|
||||
|
||||
@@ -36,6 +36,7 @@ import google.registry.flows.EppTestComponent.FakesAndMocksModule;
|
||||
import google.registry.flows.picker.FlowPicker;
|
||||
import google.registry.model.billing.BillingBase;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.eppcommon.EppXmlTransformer;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
@@ -134,28 +135,6 @@ public abstract class FlowTestCase<F extends Flow> {
|
||||
return TestDataHelper.loadFile(getClass(), filename, substitutions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an input or response EPP message with draft fee extension v12 to std v1.
|
||||
*
|
||||
* <p>There is no practical changes between draft v12 and the v1 standard. This method allows us
|
||||
* to reuse v12 test data.
|
||||
*/
|
||||
protected String loadFeeV12FileAsStdV1(String filename) {
|
||||
String content = loadFile(filename);
|
||||
return content.replace("urn:ietf:params:xml:ns:fee-0.12", "urn:ietf:params:xml:ns:epp:fee-1.0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an input or response EPP message with draft fee extension v12 to std v1.
|
||||
*
|
||||
* <p>There is no practical changes between draft v12 and the v1 standard. This method allows us
|
||||
* to reuse v12 test data.
|
||||
*/
|
||||
protected String loadFeeV12FileAsStdV1(String filename, Map<String, String> substitutions) {
|
||||
String content = loadFile(filename, substitutions);
|
||||
return content.replace("urn:ietf:params:xml:ns:fee-0.12", "urn:ietf:params:xml:ns:epp:fee-1.0");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected String getClientTrid() throws Exception {
|
||||
return eppLoader.getEpp().getCommandWrapper().getClTrid().orElse(null);
|
||||
@@ -289,6 +268,8 @@ public abstract class FlowTestCase<F extends Flow> {
|
||||
if (output.isResponse()) {
|
||||
assertThat(output.isSuccess()).isTrue();
|
||||
}
|
||||
// Verify that expected xml is syntatically correct.
|
||||
EppXmlTransformer.validateOutput(xml);
|
||||
try {
|
||||
assertXmlEquals(
|
||||
xml, new String(marshal(output, ValidationMode.STRICT), UTF_8), ignoredPathsPlusTrid);
|
||||
|
||||
@@ -14,85 +14,25 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.model.eppoutput.CheckData.ContactCheck.create;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistDeletedContact;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.ResourceCheckFlowTestCase;
|
||||
import google.registry.flows.exceptions.TooManyResourceChecksException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.flows.FlowTestCase;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactCheckFlow}. */
|
||||
class ContactCheckFlowTest extends ResourceCheckFlowTestCase<ContactCheckFlow, Contact> {
|
||||
class ContactCheckFlowTest extends FlowTestCase<ContactCheckFlow> {
|
||||
|
||||
ContactCheckFlowTest() {
|
||||
setEppInput("contact_check.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotLoggedIn() {
|
||||
sessionMetadata.setRegistrarId(null);
|
||||
EppException thrown = assertThrows(NotLoggedInException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
void testThrowsException() {
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(ContactsProhibitedException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNothingExists() throws Exception {
|
||||
// These ids come from the check xml.
|
||||
doCheckTest(
|
||||
create(true, "sh8013", null),
|
||||
create(true, "sah8013", null),
|
||||
create(true, "8013sah", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOneExists() throws Exception {
|
||||
persistActiveContact("sh8013");
|
||||
// These ids come from the check xml.
|
||||
doCheckTest(
|
||||
create(false, "sh8013", "In use"),
|
||||
create(true, "sah8013", null),
|
||||
create(true, "8013sah", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOneExistsButWasDeleted() throws Exception {
|
||||
persistDeletedContact("sh8013", clock.nowUtc().minusDays(1));
|
||||
// These ids come from the check xml.
|
||||
doCheckTest(
|
||||
create(true, "sh8013", null),
|
||||
create(true, "sah8013", null),
|
||||
create(true, "8013sah", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testXmlMatches() throws Exception {
|
||||
persistActiveContact("sah8013");
|
||||
runFlowAssertResponse(loadFile("contact_check_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test50IdsAllowed() throws Exception {
|
||||
// Make sure we don't have a regression that reduces the number of allowed checks.
|
||||
setEppInput("contact_check_50.xml");
|
||||
runFlow();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTooManyIds() {
|
||||
setEppInput("contact_check_51.xml");
|
||||
EppException thrown = assertThrows(TooManyResourceChecksException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-check");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,141 +14,24 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
|
||||
import static google.registry.testing.ContactSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.newContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistDeletedContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.ResourceFlowTestCase;
|
||||
import google.registry.flows.contact.ContactFlowUtils.BadInternationalizedPostalInfoException;
|
||||
import google.registry.flows.contact.ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException;
|
||||
import google.registry.flows.FlowTestCase;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
|
||||
import google.registry.flows.exceptions.ResourceCreateContentionException;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.contact.Contact;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactCreateFlow}. */
|
||||
class ContactCreateFlowTest extends ResourceFlowTestCase<ContactCreateFlow, Contact> {
|
||||
class ContactCreateFlowTest extends FlowTestCase<ContactCreateFlow> {
|
||||
|
||||
ContactCreateFlowTest() {
|
||||
setEppInput("contact_create.xml");
|
||||
clock.setTo(DateTime.parse("1999-04-03T22:00:00.0Z"));
|
||||
}
|
||||
|
||||
private void doSuccessfulTest() throws Exception {
|
||||
assertMutatingFlow(true);
|
||||
runFlowAssertResponse(loadFile("contact_create_response.xml"));
|
||||
// Check that the contact was created and persisted with a history entry.
|
||||
Contact contact = reloadResourceByForeignKey();
|
||||
assertAboutContacts().that(contact).hasOnlyOneHistoryEntryWhich().hasNoXml();
|
||||
assertNoBillingEvents();
|
||||
assertLastHistoryContainsResource(contact);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotLoggedIn() {
|
||||
sessionMetadata.setRegistrarId(null);
|
||||
EppException thrown = assertThrows(NotLoggedInException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDryRun() throws Exception {
|
||||
dryRunFlowAssertResponse(loadFile("contact_create_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_neverExisted() throws Exception {
|
||||
doSuccessfulTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_existedButWasDeleted() throws Exception {
|
||||
persistDeletedContact(getUniqueIdFromCommand(), clock.nowUtc().minusDays(1));
|
||||
clock.advanceOneMilli();
|
||||
doSuccessfulTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_alreadyExists() throws Exception {
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
ResourceAlreadyExistsForThisClientException thrown =
|
||||
assertThrows(ResourceAlreadyExistsForThisClientException.class, this::runFlow);
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains(
|
||||
String.format("Object with given ID (%s) already exists", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_minimumDatasetPhase2_cannotCreateContacts() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
|
||||
.build());
|
||||
EppException thrown = assertThrows(ContactsProhibitedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_resourceContention() throws Exception {
|
||||
String targetId = getUniqueIdFromCommand();
|
||||
persistResource(
|
||||
newContact(targetId)
|
||||
.asBuilder()
|
||||
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
|
||||
.build());
|
||||
ResourceCreateContentionException thrown =
|
||||
assertThrows(ResourceCreateContentionException.class, this::runFlow);
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.contains(String.format("Object with given ID (%s) already exists", targetId));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_nonAsciiInLocAddress() throws Exception {
|
||||
setEppInput("contact_create_hebrew_loc.xml");
|
||||
doSuccessfulTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nonAsciiInIntAddress() {
|
||||
setEppInput("contact_create_hebrew_int.xml");
|
||||
EppException thrown =
|
||||
assertThrows(BadInternationalizedPostalInfoException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_declineDisclosure() {
|
||||
setEppInput("contact_create_decline_disclosure.xml");
|
||||
EppException thrown =
|
||||
assertThrows(DeclineContactDisclosureFieldDisallowedPolicyException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-create");
|
||||
void testThrowsException() {
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(ContactsProhibitedException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,269 +14,24 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.ContactSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.getPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.newContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistContactWithPendingTransfer;
|
||||
import static google.registry.testing.DatabaseHelper.persistDeletedContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.ResourceFlowTestCase;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
|
||||
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
|
||||
import google.registry.flows.exceptions.ResourceToDeleteIsReferencedException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntry.Type;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import google.registry.flows.FlowTestCase;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactDeleteFlow}. */
|
||||
class ContactDeleteFlowTest extends ResourceFlowTestCase<ContactDeleteFlow, Contact> {
|
||||
class ContactDeleteFlowTest extends FlowTestCase<ContactDeleteFlow> {
|
||||
|
||||
@BeforeEach
|
||||
void initFlowTest() {
|
||||
ContactDeleteFlowTest() {
|
||||
setEppInput("contact_delete.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotLoggedIn() {
|
||||
sessionMetadata.setRegistrarId(null);
|
||||
EppException thrown = assertThrows(NotLoggedInException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDryRun() throws Exception {
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
dryRunFlowAssertResponse(loadFile("contact_delete_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess() throws Exception {
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
clock.advanceOneMilli();
|
||||
assertMutatingFlow(true);
|
||||
runFlowAssertResponse(loadFile("contact_delete_response.xml"));
|
||||
assertSqlDeleteSuccess();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_pendingTransfer_sql() throws Exception {
|
||||
DateTime transferRequestTime = clock.nowUtc().minusDays(3);
|
||||
TransferData oldTransferData =
|
||||
persistContactWithPendingTransfer(
|
||||
persistActiveContact(getUniqueIdFromCommand()),
|
||||
transferRequestTime,
|
||||
transferRequestTime.plus(Tld.DEFAULT_TRANSFER_GRACE_PERIOD),
|
||||
clock.nowUtc())
|
||||
.getTransferData();
|
||||
clock.advanceOneMilli();
|
||||
assertMutatingFlow(true);
|
||||
runFlowAssertResponse(loadFile("contact_delete_response.xml"));
|
||||
assertSqlDeleteSuccess(Type.CONTACT_DELETE, Type.CONTACT_TRANSFER_REQUEST);
|
||||
Contact softDeletedContact = reloadResourceByForeignKey(clock.nowUtc().minusMillis(1));
|
||||
assertThat(softDeletedContact.getTransferData())
|
||||
.isEqualTo(
|
||||
oldTransferData
|
||||
.copyConstantFieldsToBuilder()
|
||||
.setTransferStatus(TransferStatus.SERVER_CANCELLED)
|
||||
.setPendingTransferExpirationTime(softDeletedContact.getDeletionTime())
|
||||
.build());
|
||||
PollMessage gainingPollMessage =
|
||||
Iterables.getOnlyElement(getPollMessages("NewRegistrar", clock.nowUtc()));
|
||||
assertThat(gainingPollMessage.getEventTime()).isEqualTo(clock.nowUtc());
|
||||
assertThat(
|
||||
gainingPollMessage.getResponseData().stream()
|
||||
.filter(TransferResponse.class::isInstance)
|
||||
.map(TransferResponse.class::cast)
|
||||
.collect(onlyElement())
|
||||
.getTransferStatus())
|
||||
.isEqualTo(TransferStatus.SERVER_CANCELLED);
|
||||
PendingActionNotificationResponse panData =
|
||||
gainingPollMessage.getResponseData().stream()
|
||||
.filter(PendingActionNotificationResponse.class::isInstance)
|
||||
.map(PendingActionNotificationResponse.class::cast)
|
||||
.collect(onlyElement());
|
||||
assertThat(panData.getTrid())
|
||||
.isEqualTo(Trid.create("transferClient-trid", "transferServer-trid"));
|
||||
assertThat(panData.getActionResult()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clTridNotSpecified() throws Exception {
|
||||
setEppInput("contact_delete_no_cltrid.xml");
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
clock.advanceOneMilli();
|
||||
assertMutatingFlow(true);
|
||||
runFlowAssertResponse(loadFile("contact_delete_response_no_cltrid.xml"));
|
||||
assertSqlDeleteSuccess();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_neverExisted() throws Exception {
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(ResourceDoesNotExistException.class, this::runFlow);
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_existedButWasDeleted() throws Exception {
|
||||
persistDeletedContact(getUniqueIdFromCommand(), clock.nowUtc().minusDays(1));
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(ResourceDoesNotExistException.class, this::runFlow);
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_existedButWasClientDeleteProhibited() throws Exception {
|
||||
doFailingStatusTest(
|
||||
StatusValue.CLIENT_DELETE_PROHIBITED, ResourceStatusProhibitsOperationException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_existedButWasServerDeleteProhibited() throws Exception {
|
||||
doFailingStatusTest(
|
||||
StatusValue.SERVER_DELETE_PROHIBITED, ResourceStatusProhibitsOperationException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_existedButWasPendingDelete() throws Exception {
|
||||
doFailingStatusTest(
|
||||
StatusValue.PENDING_DELETE, ResourceStatusProhibitsOperationException.class);
|
||||
}
|
||||
|
||||
private void doFailingStatusTest(StatusValue statusValue, Class<? extends EppException> exception)
|
||||
throws Exception {
|
||||
persistResource(
|
||||
newContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setStatusValues(ImmutableSet.of(statusValue))
|
||||
.build());
|
||||
EppException thrown = assertThrows(exception, this::runFlow);
|
||||
assertThat(thrown).hasMessageThat().contains(statusValue.getXmlName());
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clientDeleteProhibited_superuser() throws Exception {
|
||||
persistResource(
|
||||
persistActiveContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.addStatusValue(StatusValue.CLIENT_DELETE_PROHIBITED)
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("contact_delete_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_serverDeleteProhibited_superuser() throws Exception {
|
||||
persistResource(
|
||||
persistActiveContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.addStatusValue(StatusValue.SERVER_DELETE_PROHIBITED)
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("contact_delete_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_pendingDelete_superuser() throws Exception {
|
||||
persistResource(
|
||||
persistActiveContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.addStatusValue(StatusValue.PENDING_DELETE)
|
||||
.build());
|
||||
void testThrowsException() {
|
||||
assertAboutEppExceptions()
|
||||
.that(
|
||||
assertThrows(
|
||||
ResourceStatusProhibitsOperationException.class,
|
||||
() -> runFlow(CommitMode.LIVE, UserPrivileges.SUPERUSER)))
|
||||
.that(assertThrows(ContactsProhibitedException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_unauthorizedClient() throws Exception {
|
||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
EppException thrown = assertThrows(ResourceNotOwnedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_superuserUnauthorizedClient() throws Exception {
|
||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("contact_delete_response.xml"));
|
||||
assertSqlDeleteSuccess();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_failfastWhenLinkedToDomain() throws Exception {
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain("example.tld", persistActiveContact(getUniqueIdFromCommand())));
|
||||
EppException thrown = assertThrows(ResourceToDeleteIsReferencedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
clock.advanceOneMilli();
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-delete");
|
||||
}
|
||||
|
||||
private void assertSqlDeleteSuccess(HistoryEntry.Type... historyEntryTypes) throws Exception {
|
||||
assertThat(reloadResourceByForeignKey()).isNull();
|
||||
assertAboutContacts()
|
||||
.that(reloadResourceByForeignKey(clock.nowUtc().minusMillis(1)))
|
||||
.isNotActiveAt(clock.nowUtc())
|
||||
.and()
|
||||
.hasNullLocalizedPostalInfo()
|
||||
.and()
|
||||
.hasNullInternationalizedPostalInfo()
|
||||
.and()
|
||||
.hasNullEmailAddress()
|
||||
.and()
|
||||
.hasNullVoiceNumber()
|
||||
.and()
|
||||
.hasNullFaxNumber()
|
||||
.and()
|
||||
.hasExactlyStatusValues(StatusValue.OK)
|
||||
.and()
|
||||
.hasOneHistoryEntryEachOfTypes(historyEntryTypes);
|
||||
assertNoBillingEvents();
|
||||
}
|
||||
|
||||
private void assertSqlDeleteSuccess() throws Exception {
|
||||
assertSqlDeleteSuccess(Type.CONTACT_DELETE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,202 +14,24 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.EppResourceUtils.isDeleted;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.ResourceFlowTestCase;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAddress;
|
||||
import google.registry.model.contact.ContactAuthInfo;
|
||||
import google.registry.model.contact.ContactPhoneNumber;
|
||||
import google.registry.model.contact.Disclose;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.contact.PostalInfo.Type;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.model.eppcommon.PresenceMarker;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import org.joda.time.DateTime;
|
||||
import google.registry.flows.FlowTestCase;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactInfoFlow}. */
|
||||
class ContactInfoFlowTest extends ResourceFlowTestCase<ContactInfoFlow, Contact> {
|
||||
class ContactInfoFlowTest extends FlowTestCase<ContactInfoFlow> {
|
||||
|
||||
ContactInfoFlowTest() {
|
||||
setEppInput("contact_info.xml");
|
||||
}
|
||||
|
||||
private Contact persistContact(boolean active) {
|
||||
Contact contact =
|
||||
persistResource(
|
||||
new Contact.Builder()
|
||||
.setContactId("sh8013")
|
||||
.setRepoId("2FF-ROID")
|
||||
.setDeletionTime(active ? null : clock.nowUtc().minusDays(1))
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.CLIENT_DELETE_PROHIBITED))
|
||||
.setInternationalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(Type.INTERNATIONALIZED)
|
||||
.setName("John Doe")
|
||||
.setOrg("Example Inc.")
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("123 Example Dr.", "Suite 100"))
|
||||
.setCity("Dulles")
|
||||
.setState("VA")
|
||||
.setZip("20166-6503")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build())
|
||||
.setVoiceNumber(
|
||||
new ContactPhoneNumber.Builder()
|
||||
.setPhoneNumber("+1.7035555555")
|
||||
.setExtension("1234")
|
||||
.build())
|
||||
.setFaxNumber(
|
||||
new ContactPhoneNumber.Builder().setPhoneNumber("+1.7035555556").build())
|
||||
.setEmailAddress("jdoe@example.com")
|
||||
.setPersistedCurrentSponsorRegistrarId("TheRegistrar")
|
||||
.setCreationRegistrarId("NewRegistrar")
|
||||
.setLastEppUpdateRegistrarId("NewRegistrar")
|
||||
.setCreationTimeForTest(DateTime.parse("1999-04-03T22:00:00.0Z"))
|
||||
.setLastEppUpdateTime(DateTime.parse("1999-12-03T09:00:00.0Z"))
|
||||
.setLastTransferTime(DateTime.parse("2000-04-08T09:00:00.0Z"))
|
||||
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("2fooBAR")))
|
||||
.setDisclose(
|
||||
new Disclose.Builder()
|
||||
.setFlag(true)
|
||||
.setVoice(new PresenceMarker())
|
||||
.setEmail(new PresenceMarker())
|
||||
.build())
|
||||
.build());
|
||||
assertThat(isDeleted(contact, clock.nowUtc())).isNotEqualTo(active);
|
||||
return contact;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotLoggedIn() {
|
||||
sessionMetadata.setRegistrarId(null);
|
||||
EppException thrown = assertThrows(NotLoggedInException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess() throws Exception {
|
||||
persistContact(true);
|
||||
// Check that the persisted contact info was returned.
|
||||
assertMutatingFlow(false);
|
||||
runFlowAssertResponse(
|
||||
loadFile("contact_info_response.xml"),
|
||||
// We use a different roid scheme than the samples so ignore it.
|
||||
"epp.response.resData.infData.roid");
|
||||
assertNoHistory();
|
||||
assertNoBillingEvents();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_linked() throws Exception {
|
||||
createTld("foobar");
|
||||
persistResource(DatabaseHelper.newDomain("example.foobar", persistContact(true)));
|
||||
// Check that the persisted contact info was returned.
|
||||
assertMutatingFlow(false);
|
||||
runFlowAssertResponse(
|
||||
loadFile("contact_info_response_linked.xml"),
|
||||
// We use a different roid scheme than the samples so ignore it.
|
||||
"epp.response.resData.infData.roid");
|
||||
assertNoHistory();
|
||||
assertNoBillingEvents();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_owningRegistrarWithoutAuthInfo_seesAuthInfo() throws Exception {
|
||||
setEppInput("contact_info_no_authinfo.xml");
|
||||
persistContact(true);
|
||||
// Check that the persisted contact info was returned.
|
||||
assertMutatingFlow(false);
|
||||
runFlowAssertResponse(
|
||||
loadFile("contact_info_response.xml"),
|
||||
// We use a different roid scheme than the samples so ignore it.
|
||||
"epp.response.resData.infData.roid");
|
||||
assertNoHistory();
|
||||
assertNoBillingEvents();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_otherRegistrar_notAuthorized() throws Exception {
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
persistContact(true);
|
||||
// Check that the persisted contact info was returned.
|
||||
assertMutatingFlow(false);
|
||||
ResourceNotOwnedException thrown = assertThrows(ResourceNotOwnedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_otherRegistrarWithoutAuthInfoAsSuperuser_doesNotSeeAuthInfo() throws Exception {
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
setEppInput("contact_info_no_authinfo.xml");
|
||||
persistContact(true);
|
||||
// Check that the persisted contact info was returned.
|
||||
assertMutatingFlow(false);
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE,
|
||||
UserPrivileges.SUPERUSER,
|
||||
loadFile("contact_info_response_no_authinfo.xml"),
|
||||
// We use a different roid scheme than the samples so ignore it.
|
||||
"epp.response.resData.infData.roid");
|
||||
assertNoHistory();
|
||||
assertNoBillingEvents();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_otherRegistrarWithAuthInfoAsSuperuser_seesAuthInfo() throws Exception {
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
persistContact(true);
|
||||
// Check that the persisted contact info was returned.
|
||||
assertMutatingFlow(false);
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE,
|
||||
UserPrivileges.SUPERUSER,
|
||||
loadFile("contact_info_response.xml"),
|
||||
// We use a different roid scheme than the samples so ignore it.
|
||||
"epp.response.resData.infData.roid");
|
||||
assertNoHistory();
|
||||
assertNoBillingEvents();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_neverExisted() throws Exception {
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(ResourceDoesNotExistException.class, this::runFlow);
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_existedButWasDeleted() throws Exception {
|
||||
persistContact(false);
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(ResourceDoesNotExistException.class, this::runFlow);
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
persistContact(true);
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-info");
|
||||
void testThrowsException() {
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(ContactsProhibitedException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,254 +14,24 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.ContactSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
|
||||
import static google.registry.testing.DatabaseHelper.getPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
|
||||
import google.registry.flows.exceptions.NotPendingTransferException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAuthInfo;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import google.registry.flows.FlowTestCase;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactTransferApproveFlow}. */
|
||||
class ContactTransferApproveFlowTest
|
||||
extends ContactTransferFlowTestCase<ContactTransferApproveFlow, Contact> {
|
||||
class ContactTransferApproveFlowTest extends FlowTestCase<ContactTransferApproveFlow> {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ContactTransferApproveFlowTest() {
|
||||
setEppInput("contact_transfer_approve.xml");
|
||||
setRegistrarIdForFlow("TheRegistrar");
|
||||
setupContactWithPendingTransfer();
|
||||
clock.advanceOneMilli();
|
||||
createTld("foobar");
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(String commandFilename, String expectedXmlFilename)
|
||||
throws Exception {
|
||||
setEppInput(commandFilename);
|
||||
// Look in the future and make sure the poll messages for implicit ack are there.
|
||||
assertThat(getPollMessages("NewRegistrar", clock.nowUtc().plusMonths(1)))
|
||||
.hasSize(1);
|
||||
assertThat(getPollMessages("TheRegistrar", clock.nowUtc().plusMonths(1)))
|
||||
.hasSize(1);
|
||||
|
||||
// Setup done; run the test.
|
||||
contact = reloadResourceByForeignKey();
|
||||
TransferData originalTransferData = contact.getTransferData();
|
||||
assertMutatingFlow(true);
|
||||
runFlowAssertResponse(loadFile(expectedXmlFilename));
|
||||
|
||||
// Transfer should have succeeded. Verify correct fields were set.
|
||||
contact = reloadResourceByForeignKey();
|
||||
assertAboutContacts()
|
||||
.that(contact)
|
||||
.hasCurrentSponsorRegistrarId("NewRegistrar")
|
||||
.and()
|
||||
.hasLastTransferTime(clock.nowUtc())
|
||||
.and()
|
||||
.hasOneHistoryEntryEachOfTypes(
|
||||
HistoryEntry.Type.CONTACT_TRANSFER_REQUEST, HistoryEntry.Type.CONTACT_TRANSFER_APPROVE);
|
||||
assertThat(contact.getTransferData())
|
||||
.isEqualTo(
|
||||
originalTransferData.copyConstantFieldsToBuilder()
|
||||
.setTransferStatus(TransferStatus.CLIENT_APPROVED)
|
||||
.setPendingTransferExpirationTime(clock.nowUtc())
|
||||
.build());
|
||||
assertNoBillingEvents();
|
||||
// The poll message (in the future) to the losing registrar for implicit ack should be gone.
|
||||
assertThat(getPollMessages("TheRegistrar", clock.nowUtc().plusMonths(1))).isEmpty();
|
||||
// The poll message in the future to the gaining registrar should be gone too, but there
|
||||
// should be one at the current time to the gaining registrar.
|
||||
PollMessage gainingPollMessage = getOnlyPollMessage("NewRegistrar");
|
||||
assertThat(gainingPollMessage.getEventTime()).isEqualTo(clock.nowUtc());
|
||||
assertThat(
|
||||
gainingPollMessage
|
||||
.getResponseData()
|
||||
.stream()
|
||||
.filter(TransferResponse.class::isInstance)
|
||||
.map(TransferResponse.class::cast)
|
||||
.collect(onlyElement())
|
||||
.getTransferStatus())
|
||||
.isEqualTo(TransferStatus.CLIENT_APPROVED);
|
||||
PendingActionNotificationResponse panData =
|
||||
gainingPollMessage
|
||||
.getResponseData()
|
||||
.stream()
|
||||
.filter(PendingActionNotificationResponse.class::isInstance)
|
||||
.map(PendingActionNotificationResponse.class::cast)
|
||||
.collect(onlyElement());
|
||||
assertThat(panData.getTrid())
|
||||
.isEqualTo(Trid.create("transferClient-trid", "transferServer-trid"));
|
||||
assertThat(panData.getActionResult()).isTrue();
|
||||
assertLastHistoryContainsResource(contact);
|
||||
}
|
||||
|
||||
private void doFailingTest(String commandFilename) throws Exception {
|
||||
setEppInput(commandFilename);
|
||||
// Setup done; run the test.
|
||||
assertMutatingFlow(true);
|
||||
runFlow();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotLoggedIn() {
|
||||
sessionMetadata.setRegistrarId(null);
|
||||
EppException thrown = assertThrows(NotLoggedInException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDryRun() throws Exception {
|
||||
setEppInput("contact_transfer_approve.xml");
|
||||
dryRunFlowAssertResponse(loadFile("contact_transfer_approve_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_approve.xml", "contact_transfer_approve_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_withAuthinfo() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_approve_with_authinfo.xml",
|
||||
"contact_transfer_approve_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_badContactPassword() {
|
||||
// Change the contact's password so it does not match the password in the file.
|
||||
contact = persistResource(
|
||||
contact.asBuilder()
|
||||
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("badpassword")))
|
||||
.build());
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
BadAuthInfoForResourceException.class,
|
||||
() -> doFailingTest("contact_transfer_approve_with_authinfo.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_neverBeenTransferred() {
|
||||
changeTransferStatus(null);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_approve.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_clientApproved() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_approve.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_clientRejected() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_REJECTED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_approve.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_clientCancelled() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_CANCELLED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_approve.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_serverApproved() {
|
||||
changeTransferStatus(TransferStatus.SERVER_APPROVED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_approve.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_serverCancelled() {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_approve.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gainingClient() {
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
ResourceNotOwnedException.class, () -> doFailingTest("contact_transfer_approve.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_unrelatedClient() {
|
||||
setRegistrarIdForFlow("ClientZ");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
ResourceNotOwnedException.class, () -> doFailingTest("contact_transfer_approve.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_deletedContact() throws Exception {
|
||||
contact = persistResource(
|
||||
contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class,
|
||||
() -> doFailingTest("contact_transfer_approve.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nonexistentContact() throws Exception {
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
contact = persistResource(
|
||||
contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class,
|
||||
() -> doFailingTest("contact_transfer_approve.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-transfer-approve");
|
||||
void testThrowsException() {
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(ContactsProhibitedException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,240 +14,24 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.ContactSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
|
||||
import static google.registry.testing.DatabaseHelper.getPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||
import google.registry.flows.exceptions.NotPendingTransferException;
|
||||
import google.registry.flows.exceptions.NotTransferInitiatorException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAuthInfo;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import google.registry.flows.FlowTestCase;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactTransferCancelFlow}. */
|
||||
class ContactTransferCancelFlowTest
|
||||
extends ContactTransferFlowTestCase<ContactTransferCancelFlow, Contact> {
|
||||
class ContactTransferCancelFlowTest extends FlowTestCase<ContactTransferCancelFlow> {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.setEppInput("contact_transfer_cancel.xml");
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
setupContactWithPendingTransfer();
|
||||
clock.advanceOneMilli();
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(String commandFilename, String expectedXmlFilename)
|
||||
throws Exception {
|
||||
this.setEppInput(commandFilename);
|
||||
// Look in the future and make sure the poll messages for implicit ack are there.
|
||||
assertThat(getPollMessages("NewRegistrar", clock.nowUtc().plusMonths(1))).hasSize(1);
|
||||
assertThat(getPollMessages("TheRegistrar", clock.nowUtc().plusMonths(1))).hasSize(1);
|
||||
|
||||
// Setup done; run the test.
|
||||
contact = reloadResourceByForeignKey();
|
||||
TransferData originalTransferData = contact.getTransferData();
|
||||
assertMutatingFlow(true);
|
||||
runFlowAssertResponse(loadFile(expectedXmlFilename));
|
||||
|
||||
// Transfer should have been cancelled. Verify correct fields were set.
|
||||
contact = reloadResourceByForeignKey();
|
||||
assertAboutContacts()
|
||||
.that(contact)
|
||||
.hasCurrentSponsorRegistrarId("TheRegistrar")
|
||||
.and()
|
||||
.hasLastTransferTimeNotEqualTo(clock.nowUtc())
|
||||
.and()
|
||||
.hasOneHistoryEntryEachOfTypes(
|
||||
HistoryEntry.Type.CONTACT_TRANSFER_REQUEST, HistoryEntry.Type.CONTACT_TRANSFER_CANCEL);
|
||||
assertThat(contact.getTransferData())
|
||||
.isEqualTo(
|
||||
originalTransferData.copyConstantFieldsToBuilder()
|
||||
.setTransferStatus(TransferStatus.CLIENT_CANCELLED)
|
||||
.setPendingTransferExpirationTime(clock.nowUtc())
|
||||
.build());
|
||||
assertNoBillingEvents();
|
||||
// The poll message (in the future) to the gaining registrar for implicit ack should be gone.
|
||||
assertThat(getPollMessages("NewRegistrar", clock.nowUtc().plusMonths(1))).isEmpty();
|
||||
// The poll message in the future to the losing registrar should be gone too, but there
|
||||
// should be one at the current time to the losing registrar.
|
||||
PollMessage losingPollMessage = getOnlyPollMessage("TheRegistrar");
|
||||
assertThat(losingPollMessage.getEventTime()).isEqualTo(clock.nowUtc());
|
||||
assertThat(
|
||||
losingPollMessage
|
||||
.getResponseData()
|
||||
.stream()
|
||||
.filter(TransferResponse.class::isInstance)
|
||||
.map(TransferResponse.class::cast)
|
||||
.collect(onlyElement())
|
||||
.getTransferStatus())
|
||||
.isEqualTo(TransferStatus.CLIENT_CANCELLED);
|
||||
assertLastHistoryContainsResource(contact);
|
||||
}
|
||||
|
||||
private void doFailingTest(String commandFilename) throws Exception {
|
||||
this.setEppInput(commandFilename);
|
||||
// Setup done; run the test.
|
||||
assertMutatingFlow(true);
|
||||
runFlow();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotLoggedIn() {
|
||||
sessionMetadata.setRegistrarId(null);
|
||||
EppException thrown = assertThrows(NotLoggedInException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDryRun() throws Exception {
|
||||
ContactTransferCancelFlowTest() {
|
||||
setEppInput("contact_transfer_cancel.xml");
|
||||
dryRunFlowAssertResponse(loadFile("contact_transfer_cancel_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_cancel.xml", "contact_transfer_cancel_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_withAuthinfo() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_cancel_with_authinfo.xml",
|
||||
"contact_transfer_cancel_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_badContactPassword() {
|
||||
// Change the contact's password so it does not match the password in the file.
|
||||
contact =
|
||||
persistResource(
|
||||
contact
|
||||
.asBuilder()
|
||||
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("badpassword")))
|
||||
.build());
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
BadAuthInfoForResourceException.class,
|
||||
() -> doFailingTest("contact_transfer_cancel_with_authinfo.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_neverBeenTransferred() {
|
||||
changeTransferStatus(null);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_cancel.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_clientApproved() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_cancel.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_clientRejected() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_REJECTED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_cancel.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_clientCancelled() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_CANCELLED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_cancel.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_serverApproved() {
|
||||
changeTransferStatus(TransferStatus.SERVER_APPROVED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_cancel.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_serverCancelled() {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_cancel.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_sponsoringClient() {
|
||||
setRegistrarIdForFlow("TheRegistrar");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotTransferInitiatorException.class,
|
||||
() -> doFailingTest("contact_transfer_cancel.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_unrelatedClient() {
|
||||
setRegistrarIdForFlow("ClientZ");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotTransferInitiatorException.class,
|
||||
() -> doFailingTest("contact_transfer_cancel.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_deletedContact() throws Exception {
|
||||
contact =
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class,
|
||||
() -> doFailingTest("contact_transfer_cancel.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nonexistentContact() throws Exception {
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class,
|
||||
() -> doFailingTest("contact_transfer_cancel.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-transfer-cancel");
|
||||
void testThrowsException() {
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(ContactsProhibitedException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.testing.DatabaseHelper.newContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistContactWithPendingTransfer;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.ResourceFlowTestCase;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.transaction.JpaTransactionManagerExtension;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
/**
|
||||
* Base class for contact transfer flow unit tests.
|
||||
*
|
||||
* @param <F> the flow type
|
||||
* @param <R> the resource type
|
||||
*/
|
||||
abstract class ContactTransferFlowTestCase<F extends Flow, R extends EppResource>
|
||||
extends ResourceFlowTestCase<F, R> {
|
||||
|
||||
// Transfer is requested on the 6th and expires on the 11th.
|
||||
// The "now" of this flow is on the 9th, 3 days in.
|
||||
|
||||
private static final DateTime TRANSFER_REQUEST_TIME = DateTime.parse("2000-06-06T22:00:00.0Z");
|
||||
private static final DateTime TRANSFER_EXPIRATION_TIME =
|
||||
TRANSFER_REQUEST_TIME.plus(Tld.DEFAULT_TRANSFER_GRACE_PERIOD);
|
||||
private static final Duration TIME_SINCE_REQUEST = Duration.standardDays(3);
|
||||
|
||||
protected Contact contact;
|
||||
|
||||
ContactTransferFlowTestCase() {
|
||||
checkState(!Tld.DEFAULT_TRANSFER_GRACE_PERIOD.isShorterThan(TIME_SINCE_REQUEST));
|
||||
clock.setTo(TRANSFER_REQUEST_TIME.plus(TIME_SINCE_REQUEST));
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEachContactTransferFlowTestCase() {
|
||||
// Registrar ClientZ is used in tests that need another registrar that definitely doesn't own
|
||||
// the resources in question.
|
||||
persistResource(
|
||||
JpaTransactionManagerExtension.makeRegistrar1()
|
||||
.asBuilder()
|
||||
.setRegistrarId("ClientZ")
|
||||
.build());
|
||||
}
|
||||
|
||||
/** Adds a contact that has a pending transfer on it from TheRegistrar to NewRegistrar. */
|
||||
void setupContactWithPendingTransfer() {
|
||||
contact =
|
||||
persistContactWithPendingTransfer(
|
||||
newContact("sh8013"),
|
||||
TRANSFER_REQUEST_TIME,
|
||||
TRANSFER_EXPIRATION_TIME,
|
||||
TRANSFER_REQUEST_TIME);
|
||||
}
|
||||
|
||||
/** Changes the transfer status on the persisted contact. */
|
||||
protected void changeTransferStatus(TransferStatus transferStatus) {
|
||||
contact = persistResource(
|
||||
contact.asBuilder()
|
||||
.setTransferData(
|
||||
contact.getTransferData().asBuilder().setTransferStatus(transferStatus).build())
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
}
|
||||
|
||||
/** Changes the client ID that the flow will run as. */
|
||||
@Override
|
||||
protected void setRegistrarIdForFlow(String registrarId) {
|
||||
sessionMetadata.setRegistrarId(registrarId);
|
||||
}
|
||||
}
|
||||
@@ -14,206 +14,24 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.ContactSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||
import google.registry.flows.exceptions.NoTransferHistoryToQueryException;
|
||||
import google.registry.flows.exceptions.NotAuthorizedToViewTransferException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAuthInfo;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import google.registry.flows.FlowTestCase;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactTransferQueryFlow}. */
|
||||
class ContactTransferQueryFlowTest
|
||||
extends ContactTransferFlowTestCase<ContactTransferQueryFlow, Contact> {
|
||||
class ContactTransferQueryFlowTest extends FlowTestCase<ContactTransferQueryFlow> {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ContactTransferQueryFlowTest() {
|
||||
setEppInput("contact_transfer_query.xml");
|
||||
clock.setTo(DateTime.parse("2000-06-10T22:00:00.0Z"));
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
setupContactWithPendingTransfer();
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(String commandFilename, String expectedXmlFilename)
|
||||
throws Exception {
|
||||
setEppInput(commandFilename);
|
||||
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
|
||||
// Setup done; run the test.
|
||||
assertMutatingFlow(false);
|
||||
runFlowAssertResponse(loadFile(expectedXmlFilename));
|
||||
assertAboutContacts().that(reloadResourceByForeignKey(clock.nowUtc().minusDays(1)))
|
||||
.hasOneHistoryEntryEachOfTypes(HistoryEntry.Type.CONTACT_TRANSFER_REQUEST);
|
||||
assertNoBillingEvents();
|
||||
}
|
||||
|
||||
private void doFailingTest(String commandFilename) throws Exception {
|
||||
setEppInput(commandFilename);
|
||||
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
|
||||
// Setup done; run the test.
|
||||
assertMutatingFlow(false);
|
||||
runFlow();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotLoggedIn() {
|
||||
sessionMetadata.setRegistrarId(null);
|
||||
EppException thrown = assertThrows(NotLoggedInException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_query.xml", "contact_transfer_query_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_withContactRoid() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_query_with_roid.xml", "contact_transfer_query_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_sponsoringClient() throws Exception {
|
||||
setRegistrarIdForFlow("TheRegistrar");
|
||||
doSuccessfulTest("contact_transfer_query.xml", "contact_transfer_query_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_withAuthinfo() throws Exception {
|
||||
setRegistrarIdForFlow("ClientZ");
|
||||
doSuccessfulTest("contact_transfer_query_with_authinfo.xml",
|
||||
"contact_transfer_query_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clientApproved() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
|
||||
doSuccessfulTest("contact_transfer_query.xml",
|
||||
"contact_transfer_query_response_client_approved.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clientRejected() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_REJECTED);
|
||||
doSuccessfulTest("contact_transfer_query.xml",
|
||||
"contact_transfer_query_response_client_rejected.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clientCancelled() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_CANCELLED);
|
||||
doSuccessfulTest("contact_transfer_query.xml",
|
||||
"contact_transfer_query_response_client_cancelled.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_serverApproved() throws Exception {
|
||||
changeTransferStatus(TransferStatus.SERVER_APPROVED);
|
||||
doSuccessfulTest("contact_transfer_query.xml",
|
||||
"contact_transfer_query_response_server_approved.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_serverCancelled() throws Exception {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
doSuccessfulTest("contact_transfer_query.xml",
|
||||
"contact_transfer_query_response_server_cancelled.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_pendingDeleteContact() throws Exception {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
contact = persistResource(
|
||||
contact.asBuilder().setDeletionTime(clock.nowUtc().plusDays(1)).build());
|
||||
doSuccessfulTest("contact_transfer_query.xml",
|
||||
"contact_transfer_query_response_server_cancelled.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_badContactPassword() {
|
||||
// Change the contact's password so it does not match the password in the file.
|
||||
contact =
|
||||
persistResource(
|
||||
contact
|
||||
.asBuilder()
|
||||
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("badpassword")))
|
||||
.build());
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
BadAuthInfoForResourceException.class,
|
||||
() -> doFailingTest("contact_transfer_query_with_authinfo.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_badContactRoid() {
|
||||
// Set the contact to a different ROID, but don't persist it; this is just so the substitution
|
||||
// code above will write the wrong ROID into the file.
|
||||
contact = contact.asBuilder().setRepoId("DEADBEEF_TLD-ROID").build();
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
BadAuthInfoForResourceException.class,
|
||||
() -> doFailingTest("contact_transfer_query_with_roid.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_neverBeenTransferred() {
|
||||
changeTransferStatus(null);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NoTransferHistoryToQueryException.class,
|
||||
() -> doFailingTest("contact_transfer_query.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_unrelatedClient() {
|
||||
setRegistrarIdForFlow("ClientZ");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotAuthorizedToViewTransferException.class,
|
||||
() -> doFailingTest("contact_transfer_query.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_deletedContact() throws Exception {
|
||||
contact =
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class, () -> doFailingTest("contact_transfer_query.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nonexistentContact() throws Exception {
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class, () -> doFailingTest("contact_transfer_query.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-transfer-query");
|
||||
void testThrowsException() {
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(ContactsProhibitedException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,253 +14,24 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.ContactSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
|
||||
import static google.registry.testing.DatabaseHelper.getPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
|
||||
import google.registry.flows.exceptions.NotPendingTransferException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAuthInfo;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import google.registry.flows.FlowTestCase;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactTransferRejectFlow}. */
|
||||
class ContactTransferRejectFlowTest
|
||||
extends ContactTransferFlowTestCase<ContactTransferRejectFlow, Contact> {
|
||||
class ContactTransferRejectFlowTest extends FlowTestCase<ContactTransferRejectFlow> {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ContactTransferRejectFlowTest() {
|
||||
setEppInput("contact_transfer_reject.xml");
|
||||
setRegistrarIdForFlow("TheRegistrar");
|
||||
setupContactWithPendingTransfer();
|
||||
clock.advanceOneMilli();
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(String commandFilename, String expectedXmlFilename)
|
||||
throws Exception {
|
||||
setEppInput(commandFilename);
|
||||
// Look in the future and make sure the poll messages for implicit ack are there.
|
||||
assertThat(getPollMessages("NewRegistrar", clock.nowUtc().plusMonths(1)))
|
||||
.hasSize(1);
|
||||
assertThat(getPollMessages("TheRegistrar", clock.nowUtc().plusMonths(1)))
|
||||
.hasSize(1);
|
||||
|
||||
// Setup done; run the test.
|
||||
contact = reloadResourceByForeignKey();
|
||||
TransferData originalTransferData = contact.getTransferData();
|
||||
assertMutatingFlow(true);
|
||||
runFlowAssertResponse(loadFile(expectedXmlFilename));
|
||||
|
||||
// Transfer should have failed. Verify correct fields were set.
|
||||
contact = reloadResourceByForeignKey();
|
||||
assertAboutContacts()
|
||||
.that(contact)
|
||||
.hasCurrentSponsorRegistrarId("TheRegistrar")
|
||||
.and()
|
||||
.hasLastTransferTimeNotEqualTo(clock.nowUtc())
|
||||
.and()
|
||||
.hasOneHistoryEntryEachOfTypes(
|
||||
HistoryEntry.Type.CONTACT_TRANSFER_REQUEST, HistoryEntry.Type.CONTACT_TRANSFER_REJECT);
|
||||
assertThat(contact.getTransferData())
|
||||
.isEqualTo(
|
||||
originalTransferData.copyConstantFieldsToBuilder()
|
||||
.setTransferStatus(TransferStatus.CLIENT_REJECTED)
|
||||
.setPendingTransferExpirationTime(clock.nowUtc())
|
||||
.build());
|
||||
// The poll message (in the future) to the losing registrar for implicit ack should be gone.
|
||||
assertThat(getPollMessages("TheRegistrar", clock.nowUtc().plusMonths(1)))
|
||||
.isEmpty();
|
||||
// The poll message in the future to the gaining registrar should be gone too, but there
|
||||
// should be one at the current time to the gaining registrar.
|
||||
PollMessage gainingPollMessage = getOnlyPollMessage("NewRegistrar");
|
||||
assertThat(gainingPollMessage.getEventTime()).isEqualTo(clock.nowUtc());
|
||||
assertThat(
|
||||
gainingPollMessage
|
||||
.getResponseData()
|
||||
.stream()
|
||||
.filter(TransferResponse.class::isInstance)
|
||||
.map(TransferResponse.class::cast)
|
||||
.collect(onlyElement())
|
||||
.getTransferStatus())
|
||||
.isEqualTo(TransferStatus.CLIENT_REJECTED);
|
||||
PendingActionNotificationResponse panData =
|
||||
gainingPollMessage
|
||||
.getResponseData()
|
||||
.stream()
|
||||
.filter(PendingActionNotificationResponse.class::isInstance)
|
||||
.map(PendingActionNotificationResponse.class::cast)
|
||||
.collect(onlyElement());
|
||||
assertThat(panData.getTrid())
|
||||
.isEqualTo(Trid.create("transferClient-trid", "transferServer-trid"));
|
||||
assertThat(panData.getActionResult()).isFalse();
|
||||
assertNoBillingEvents();
|
||||
assertLastHistoryContainsResource(contact);
|
||||
}
|
||||
|
||||
private void doFailingTest(String commandFilename) throws Exception {
|
||||
setEppInput(commandFilename);
|
||||
// Setup done; run the test.
|
||||
assertMutatingFlow(true);
|
||||
runFlow();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotLoggedIn() {
|
||||
sessionMetadata.setRegistrarId(null);
|
||||
EppException thrown = assertThrows(NotLoggedInException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDryRun() throws Exception {
|
||||
setEppInput("contact_transfer_reject.xml");
|
||||
dryRunFlowAssertResponse(loadFile("contact_transfer_reject_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_reject.xml", "contact_transfer_reject_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_domainAuthInfo() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_reject_with_authinfo.xml",
|
||||
"contact_transfer_reject_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_badPassword() {
|
||||
// Change the contact's password so it does not match the password in the file.
|
||||
contact =
|
||||
persistResource(
|
||||
contact
|
||||
.asBuilder()
|
||||
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("badpassword")))
|
||||
.build());
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
BadAuthInfoForResourceException.class,
|
||||
() -> doFailingTest("contact_transfer_reject_with_authinfo.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_neverBeenTransferred() {
|
||||
changeTransferStatus(null);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_reject.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_clientApproved() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_reject.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_clientRejected() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_REJECTED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_reject.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_clientCancelled() {
|
||||
changeTransferStatus(TransferStatus.CLIENT_CANCELLED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_reject.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_serverApproved() {
|
||||
changeTransferStatus(TransferStatus.SERVER_APPROVED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_reject.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_serverCancelled() {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
NotPendingTransferException.class, () -> doFailingTest("contact_transfer_reject.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gainingClient() {
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
ResourceNotOwnedException.class, () -> doFailingTest("contact_transfer_reject.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_unrelatedClient() {
|
||||
setRegistrarIdForFlow("ClientZ");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
ResourceNotOwnedException.class, () -> doFailingTest("contact_transfer_reject.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_deletedContact() throws Exception {
|
||||
contact =
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class,
|
||||
() -> doFailingTest("contact_transfer_reject.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nonexistentContact() throws Exception {
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class,
|
||||
() -> doFailingTest("contact_transfer_reject.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-transfer-reject");
|
||||
void testThrowsException() {
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(ContactsProhibitedException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,304 +14,24 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.base.Predicates.equalTo;
|
||||
import static com.google.common.base.Predicates.not;
|
||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||
import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.config.RegistryConfig.getContactAutomaticTransferLength;
|
||||
import static google.registry.testing.ContactSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.assertPollMessagesEqual;
|
||||
import static google.registry.testing.DatabaseHelper.deleteResource;
|
||||
import static google.registry.testing.DatabaseHelper.getPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.loadByKeys;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
||||
import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException;
|
||||
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
||||
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAuthInfo;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.ContactTransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import google.registry.flows.FlowTestCase;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactTransferRequestFlow}. */
|
||||
class ContactTransferRequestFlowTest
|
||||
extends ContactTransferFlowTestCase<ContactTransferRequestFlow, Contact> {
|
||||
class ContactTransferRequestFlowTest extends FlowTestCase<ContactTransferRequestFlow> {
|
||||
|
||||
ContactTransferRequestFlowTest() {
|
||||
// We need the transfer to happen at exactly this time in order for the response to match up.
|
||||
clock.setTo(DateTime.parse("2000-06-08T22:00:00.0Z"));
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
setEppInput("contact_transfer_request.xml");
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
contact = persistActiveContact("sh8013");
|
||||
clock.advanceOneMilli();
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(String commandFilename, String expectedXmlFilename)
|
||||
throws Exception {
|
||||
setEppInput(commandFilename);
|
||||
DateTime afterTransfer = clock.nowUtc().plus(getContactAutomaticTransferLength());
|
||||
|
||||
// Setup done; run the test.
|
||||
assertMutatingFlow(true);
|
||||
runFlowAssertResponse(loadFile(expectedXmlFilename));
|
||||
|
||||
// Transfer should have been requested. Verify correct fields were set.
|
||||
contact = reloadResourceByForeignKey();
|
||||
assertAboutContacts()
|
||||
.that(contact)
|
||||
.hasCurrentSponsorRegistrarId("TheRegistrar")
|
||||
.and()
|
||||
.hasOnlyOneHistoryEntryWhich()
|
||||
.hasType(HistoryEntry.Type.CONTACT_TRANSFER_REQUEST);
|
||||
Trid expectedTrid =
|
||||
Trid.create(
|
||||
getClientTrid(),
|
||||
contact.getTransferData().getTransferRequestTrid().getServerTransactionId());
|
||||
assertThat(contact.getTransferData())
|
||||
.isEqualTo(
|
||||
new ContactTransferData.Builder()
|
||||
.setTransferRequestTrid(expectedTrid)
|
||||
.setTransferRequestTime(clock.nowUtc())
|
||||
.setGainingRegistrarId("NewRegistrar")
|
||||
.setLosingRegistrarId("TheRegistrar")
|
||||
.setTransferStatus(TransferStatus.PENDING)
|
||||
.setPendingTransferExpirationTime(afterTransfer)
|
||||
// Make the server-approve entities field a no-op comparison; it's easier to
|
||||
// do this comparison separately below.
|
||||
.setServerApproveEntities(
|
||||
contact.getRepoId(),
|
||||
contact.getTransferData().getHistoryEntryId(),
|
||||
forceEmptyToNull(contact.getTransferData().getServerApproveEntities()))
|
||||
.build());
|
||||
assertNoBillingEvents();
|
||||
assertThat(getPollMessages("TheRegistrar", clock.nowUtc())).hasSize(1);
|
||||
PollMessage losingRequestMessage =
|
||||
getOnlyElement(getPollMessages("TheRegistrar", clock.nowUtc()));
|
||||
|
||||
// If we fast forward AUTOMATIC_TRANSFER_DAYS the transfer should have happened.
|
||||
assertAboutContacts()
|
||||
.that(contact.cloneProjectedAtTime(afterTransfer))
|
||||
.hasCurrentSponsorRegistrarId("NewRegistrar");
|
||||
assertThat(getPollMessages("NewRegistrar", afterTransfer)).hasSize(1);
|
||||
assertThat(getPollMessages("TheRegistrar", afterTransfer)).hasSize(2);
|
||||
PollMessage gainingApproveMessage =
|
||||
getOnlyElement(getPollMessages("NewRegistrar", afterTransfer));
|
||||
PollMessage losingApproveMessage =
|
||||
getPollMessages("TheRegistrar", afterTransfer)
|
||||
.stream()
|
||||
.filter(not(equalTo(losingRequestMessage)))
|
||||
.collect(onlyElement());
|
||||
|
||||
// Check for TransferData server-approve entities containing what we expect: only
|
||||
// poll messages, the approval notice ones for gaining and losing registrars.
|
||||
assertPollMessagesEqual(
|
||||
Iterables.filter(
|
||||
loadByKeys(contact.getTransferData().getServerApproveEntities()), PollMessage.class),
|
||||
ImmutableList.of(gainingApproveMessage, losingApproveMessage));
|
||||
assertLastHistoryContainsResource(contact);
|
||||
}
|
||||
|
||||
private void doFailingTest(String commandFilename) throws Exception {
|
||||
setEppInput(commandFilename);
|
||||
// Setup done; run the test.
|
||||
assertMutatingFlow(true);
|
||||
runFlow();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotLoggedIn() {
|
||||
sessionMetadata.setRegistrarId(null);
|
||||
EppException thrown = assertThrows(NotLoggedInException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDryRun() throws Exception {
|
||||
setEppInput("contact_transfer_request.xml");
|
||||
dryRunFlowAssertResponse(loadFile("contact_transfer_request_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess() throws Exception {
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_noAuthInfo() {
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
MissingTransferRequestAuthInfoException.class,
|
||||
() -> doFailingTest("contact_transfer_request_no_authinfo.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_badPassword() {
|
||||
// Change the contact's password so it does not match the password in the file.
|
||||
contact =
|
||||
persistResource(
|
||||
contact
|
||||
.asBuilder()
|
||||
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("badpassword")))
|
||||
.build());
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
BadAuthInfoForResourceException.class,
|
||||
() -> doFailingTest("contact_transfer_request.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clientApproved() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clientRejected() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_REJECTED);
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clientCancelled() throws Exception {
|
||||
changeTransferStatus(TransferStatus.CLIENT_CANCELLED);
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_serverApproved() throws Exception {
|
||||
changeTransferStatus(TransferStatus.SERVER_APPROVED);
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_serverCancelled() throws Exception {
|
||||
changeTransferStatus(TransferStatus.SERVER_CANCELLED);
|
||||
doSuccessfulTest("contact_transfer_request.xml", "contact_transfer_request_response.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_pending() {
|
||||
contact =
|
||||
persistResource(
|
||||
contact
|
||||
.asBuilder()
|
||||
.setTransferData(
|
||||
contact
|
||||
.getTransferData()
|
||||
.asBuilder()
|
||||
.setTransferStatus(TransferStatus.PENDING)
|
||||
.setPendingTransferExpirationTime(clock.nowUtc().plusDays(1))
|
||||
.build())
|
||||
.build());
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
AlreadyPendingTransferException.class,
|
||||
() -> doFailingTest("contact_transfer_request.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_sponsoringClient() {
|
||||
setRegistrarIdForFlow("TheRegistrar");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
ObjectAlreadySponsoredException.class,
|
||||
() -> doFailingTest("contact_transfer_request.xml"));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_deletedContact() throws Exception {
|
||||
contact =
|
||||
persistResource(contact.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class,
|
||||
() -> doFailingTest("contact_transfer_request.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nonexistentContact() throws Exception {
|
||||
deleteResource(contact);
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(
|
||||
ResourceDoesNotExistException.class,
|
||||
() -> doFailingTest("contact_transfer_request.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_clientTransferProhibited() {
|
||||
contact =
|
||||
persistResource(
|
||||
contact.asBuilder().addStatusValue(StatusValue.CLIENT_TRANSFER_PROHIBITED).build());
|
||||
ResourceStatusProhibitsOperationException thrown =
|
||||
assertThrows(
|
||||
ResourceStatusProhibitsOperationException.class,
|
||||
() -> doFailingTest("contact_transfer_request.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains("clientTransferProhibited");
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_serverTransferProhibited() {
|
||||
contact =
|
||||
persistResource(
|
||||
contact.asBuilder().addStatusValue(StatusValue.SERVER_TRANSFER_PROHIBITED).build());
|
||||
ResourceStatusProhibitsOperationException thrown =
|
||||
assertThrows(
|
||||
ResourceStatusProhibitsOperationException.class,
|
||||
() -> doFailingTest("contact_transfer_request.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains("serverTransferProhibited");
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_pendingDelete() {
|
||||
contact =
|
||||
persistResource(contact.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build());
|
||||
ResourceStatusProhibitsOperationException thrown =
|
||||
assertThrows(
|
||||
ResourceStatusProhibitsOperationException.class,
|
||||
() -> doFailingTest("contact_transfer_request.xml"));
|
||||
assertThat(thrown).hasMessageThat().contains("pendingDelete");
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-transfer-request");
|
||||
void testThrowsException() {
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(ContactsProhibitedException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,449 +14,24 @@
|
||||
|
||||
package google.registry.flows.contact;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureName.MINIMUM_DATASET_CONTACTS_PROHIBITED;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
|
||||
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
|
||||
import static google.registry.testing.ContactSubject.assertAboutContacts;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.newContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistDeletedContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.ResourceFlowTestCase;
|
||||
import google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
|
||||
import google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException;
|
||||
import google.registry.flows.contact.ContactFlowUtils.BadInternationalizedPostalInfoException;
|
||||
import google.registry.flows.contact.ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException;
|
||||
import google.registry.flows.FlowTestCase;
|
||||
import google.registry.flows.exceptions.ContactsProhibitedException;
|
||||
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
||||
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
|
||||
import google.registry.model.common.FeatureFlag;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAddress;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.contact.PostalInfo.Type;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Unit tests for {@link ContactUpdateFlow}. */
|
||||
class ContactUpdateFlowTest extends ResourceFlowTestCase<ContactUpdateFlow, Contact> {
|
||||
class ContactUpdateFlowTest extends FlowTestCase<ContactUpdateFlow> {
|
||||
|
||||
ContactUpdateFlowTest() {
|
||||
setEppInput("contact_update.xml");
|
||||
}
|
||||
|
||||
private void doSuccessfulTest() throws Exception {
|
||||
clock.advanceOneMilli();
|
||||
assertMutatingFlow(true);
|
||||
runFlowAssertResponse(loadFile("generic_success_response.xml"));
|
||||
Contact contact = reloadResourceByForeignKey();
|
||||
// Check that the contact was updated. This value came from the xml.
|
||||
assertAboutContacts()
|
||||
.that(contact)
|
||||
.hasAuthInfoPwd("2fooBAR")
|
||||
.and()
|
||||
.hasOnlyOneHistoryEntryWhich()
|
||||
.hasNoXml();
|
||||
assertNoBillingEvents();
|
||||
assertLastHistoryContainsResource(contact);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotLoggedIn() {
|
||||
sessionMetadata.setRegistrarId(null);
|
||||
EppException thrown = assertThrows(NotLoggedInException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDryRun() throws Exception {
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
dryRunFlowAssertResponse(loadFile("generic_success_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess() throws Exception {
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
doSuccessfulTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_minimumDatasetPhase2_cannotUpdateContacts() throws Exception {
|
||||
persistResource(
|
||||
new FeatureFlag.Builder()
|
||||
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
|
||||
.setStatusMap(
|
||||
ImmutableSortedMap.of(START_OF_TIME, INACTIVE, clock.nowUtc().minusDays(5), ACTIVE))
|
||||
.build());
|
||||
EppException thrown = assertThrows(ContactsProhibitedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_updatingInternationalizedPostalInfoDeletesLocalized() throws Exception {
|
||||
Contact contact =
|
||||
persistResource(
|
||||
newContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setLocalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(Type.LOCALIZED)
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
|
||||
.setCity("New York")
|
||||
.setState("NY")
|
||||
.setZip("10011")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build())
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
// The test xml updates the internationalized postal info and should therefore implicitly delete
|
||||
// the localized one since they are treated as a pair for update purposes.
|
||||
assertAboutContacts().that(contact)
|
||||
.hasNonNullLocalizedPostalInfo().and()
|
||||
.hasNullInternationalizedPostalInfo();
|
||||
|
||||
runFlowAssertResponse(loadFile("generic_success_response.xml"));
|
||||
assertAboutContacts().that(reloadResourceByForeignKey())
|
||||
.hasNullLocalizedPostalInfo().and()
|
||||
.hasInternationalizedPostalInfo(new PostalInfo.Builder()
|
||||
.setType(Type.INTERNATIONALIZED)
|
||||
.setAddress(new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("124 Example Dr.", "Suite 200"))
|
||||
.setCity("Dulles")
|
||||
.setState("VA")
|
||||
.setZip("20166-6503")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_updatingLocalizedPostalInfoDeletesInternationalized() throws Exception {
|
||||
setEppInput("contact_update_localized.xml");
|
||||
Contact contact =
|
||||
persistResource(
|
||||
newContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setInternationalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(Type.INTERNATIONALIZED)
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
|
||||
.setCity("New York")
|
||||
.setState("NY")
|
||||
.setZip("10011")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build())
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
// The test xml updates the localized postal info and should therefore implicitly delete
|
||||
// the internationalized one since they are treated as a pair for update purposes.
|
||||
assertAboutContacts().that(contact)
|
||||
.hasNonNullInternationalizedPostalInfo().and()
|
||||
.hasNullLocalizedPostalInfo();
|
||||
|
||||
runFlowAssertResponse(loadFile("generic_success_response.xml"));
|
||||
assertAboutContacts().that(reloadResourceByForeignKey())
|
||||
.hasNullInternationalizedPostalInfo().and()
|
||||
.hasLocalizedPostalInfo(new PostalInfo.Builder()
|
||||
.setType(Type.LOCALIZED)
|
||||
.setAddress(new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("124 Example Dr.", "Suite 200"))
|
||||
.setCity("Dulles")
|
||||
.setState("VA")
|
||||
.setZip("20166-6503")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_partialPostalInfoUpdate() throws Exception {
|
||||
setEppInput("contact_update_partial_postalinfo.xml");
|
||||
persistResource(
|
||||
newContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setLocalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(Type.LOCALIZED)
|
||||
.setName("A. Person")
|
||||
.setOrg("Company Inc.")
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("123 4th st", "5th Floor"))
|
||||
.setCity("City")
|
||||
.setState("AB")
|
||||
.setZip("12345")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build())
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
// The test xml updates the address of the postal info and should leave the name untouched.
|
||||
runFlowAssertResponse(loadFile("generic_success_response.xml"));
|
||||
assertAboutContacts().that(reloadResourceByForeignKey()).hasLocalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(Type.LOCALIZED)
|
||||
.setName("A. Person")
|
||||
.setOrg("Company Inc.")
|
||||
.setAddress(new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("456 5th st"))
|
||||
.setCity("Place")
|
||||
.setState("CD")
|
||||
.setZip("54321")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_updateOnePostalInfo_touchOtherPostalInfoPreservesIt() throws Exception {
|
||||
setEppInput("contact_update_partial_postalinfo_preserve_int.xml");
|
||||
persistResource(
|
||||
newContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setLocalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(Type.LOCALIZED)
|
||||
.setName("A. Person")
|
||||
.setOrg("Company Inc.")
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("123 4th st", "5th Floor"))
|
||||
.setCity("City")
|
||||
.setState("AB")
|
||||
.setZip("12345")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build())
|
||||
.setInternationalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(Type.INTERNATIONALIZED)
|
||||
.setName("B. Person")
|
||||
.setOrg("Company Co.")
|
||||
.setAddress(
|
||||
new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("100 200th Dr.", "6th Floor"))
|
||||
.setCity("Town")
|
||||
.setState("CD")
|
||||
.setZip("67890")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build())
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
// The test xml updates the address of the localized postal info. It also sets the name of the
|
||||
// internationalized postal info to the same value it previously had, which causes it to be
|
||||
// preserved. If the xml had not mentioned the internationalized one at all it would have been
|
||||
// deleted.
|
||||
runFlowAssertResponse(loadFile("generic_success_response.xml"));
|
||||
assertAboutContacts().that(reloadResourceByForeignKey())
|
||||
.hasLocalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(Type.LOCALIZED)
|
||||
.setName("A. Person")
|
||||
.setOrg("Company Inc.")
|
||||
.setAddress(new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("456 5th st"))
|
||||
.setCity("Place")
|
||||
.setState("CD")
|
||||
.setZip("54321")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build())
|
||||
.and()
|
||||
.hasInternationalizedPostalInfo(
|
||||
new PostalInfo.Builder()
|
||||
.setType(Type.INTERNATIONALIZED)
|
||||
.setName("B. Person")
|
||||
.setOrg("Company Co.")
|
||||
.setAddress(new ContactAddress.Builder()
|
||||
.setStreet(ImmutableList.of("100 200th Dr.", "6th Floor"))
|
||||
.setCity("Town")
|
||||
.setState("CD")
|
||||
.setZip("67890")
|
||||
.setCountryCode("US")
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_neverExisted() throws Exception {
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(ResourceDoesNotExistException.class, this::runFlow);
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_existedButWasDeleted() throws Exception {
|
||||
persistDeletedContact(getUniqueIdFromCommand(), clock.nowUtc().minusDays(1));
|
||||
ResourceDoesNotExistException thrown =
|
||||
assertThrows(ResourceDoesNotExistException.class, this::runFlow);
|
||||
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", getUniqueIdFromCommand()));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_statusValueNotClientSettable() throws Exception {
|
||||
setEppInput("contact_update_prohibited_status.xml");
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
EppException thrown = assertThrows(StatusNotClientSettableException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_superuserStatusValueNotClientSettable() throws Exception {
|
||||
setEppInput("contact_update_prohibited_status.xml");
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("generic_success_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_unauthorizedClient() throws Exception {
|
||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
EppException thrown = assertThrows(ResourceNotOwnedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_superuserUnauthorizedClient() throws Exception {
|
||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("generic_success_response.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_clientUpdateProhibited_removed() throws Exception {
|
||||
setEppInput("contact_update_remove_client_update_prohibited.xml");
|
||||
persistResource(
|
||||
newContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.CLIENT_UPDATE_PROHIBITED))
|
||||
.build());
|
||||
doSuccessfulTest();
|
||||
assertAboutContacts()
|
||||
.that(reloadResourceByForeignKey())
|
||||
.doesNotHaveStatusValue(StatusValue.CLIENT_UPDATE_PROHIBITED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_superuserClientUpdateProhibited_notRemoved() throws Exception {
|
||||
setEppInput("contact_update_prohibited_status.xml");
|
||||
persistResource(
|
||||
newContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.CLIENT_UPDATE_PROHIBITED))
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("generic_success_response.xml"));
|
||||
assertAboutContacts()
|
||||
.that(reloadResourceByForeignKey())
|
||||
.hasStatusValue(StatusValue.CLIENT_UPDATE_PROHIBITED)
|
||||
.and()
|
||||
.hasStatusValue(StatusValue.SERVER_DELETE_PROHIBITED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_clientUpdateProhibited_notRemoved() throws Exception {
|
||||
persistResource(
|
||||
newContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.CLIENT_UPDATE_PROHIBITED))
|
||||
.build());
|
||||
EppException thrown =
|
||||
assertThrows(ResourceHasClientUpdateProhibitedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_serverUpdateProhibited() throws Exception {
|
||||
persistResource(
|
||||
newContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.SERVER_UPDATE_PROHIBITED))
|
||||
.build());
|
||||
ResourceStatusProhibitsOperationException thrown =
|
||||
assertThrows(ResourceStatusProhibitsOperationException.class, this::runFlow);
|
||||
assertThat(thrown).hasMessageThat().contains("serverUpdateProhibited");
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_pendingDeleteProhibited() throws Exception {
|
||||
persistResource(
|
||||
newContact(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.build());
|
||||
ResourceStatusProhibitsOperationException thrown =
|
||||
assertThrows(ResourceStatusProhibitsOperationException.class, this::runFlow);
|
||||
assertThat(thrown).hasMessageThat().contains("pendingDelete");
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_nonAsciiInLocAddress() throws Exception {
|
||||
setEppInput("contact_update_hebrew_loc.xml");
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
doSuccessfulTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_nonAsciiInIntAddress() throws Exception {
|
||||
setEppInput("contact_update_hebrew_int.xml");
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
EppException thrown =
|
||||
assertThrows(BadInternationalizedPostalInfoException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_declineDisclosure() throws Exception {
|
||||
setEppInput("contact_update_decline_disclosure.xml");
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
EppException thrown =
|
||||
assertThrows(DeclineContactDisclosureFieldDisallowedPolicyException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_addRemoveSameValue() throws Exception {
|
||||
setEppInput("contact_update_add_remove_same.xml");
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
EppException thrown = assertThrows(AddRemoveSameValueException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
persistActiveContact(getUniqueIdFromCommand());
|
||||
clock.advanceOneMilli();
|
||||
runFlow();
|
||||
assertIcannReportingActivityFieldLogged("srs-cont-update");
|
||||
void testThrowsException() {
|
||||
assertAboutEppExceptions()
|
||||
.that(assertThrows(ContactsProhibitedException.class, this::runFlow))
|
||||
.marshalsToXml();
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -209,6 +209,10 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
private static final String ENCODED_SMD =
|
||||
TmchData.readEncodedSignedMark(TmchTestData.loadFile(SMD_FILE_PATH)).getEncodedData();
|
||||
|
||||
private static final ImmutableMap<String, String> FEE_STD_1_0_MAP =
|
||||
ImmutableMap.of(
|
||||
"FEE_VERSION", "epp:fee-1.0", "FEE_NS", "fee1_00", "CURRENCY", "USD", "FEE", "15.00");
|
||||
|
||||
private AllocationToken allocationToken;
|
||||
|
||||
DomainCreateFlowTest() {
|
||||
@@ -640,132 +644,44 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v06() throws Exception {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
|
||||
void testSuccess_fee_std_v1() throws Exception {
|
||||
setEppInput("domain_create_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "24.00"));
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "FEE", "24.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v11() throws Exception {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
void testSuccess_fee_withDefaultAttributes_std_v1() throws Exception {
|
||||
setEppInput("domain_create_fee_defaults.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "24.00"));
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "FEE", "24.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v12() throws Exception {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "24.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v06() throws Exception {
|
||||
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "24.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v11() throws Exception {
|
||||
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "24.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v12() throws Exception {
|
||||
setEppInput("domain_create_fee_defaults.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
persistContactsAndHosts();
|
||||
doSuccessfulTest(
|
||||
"tld",
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "24.00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v06() {
|
||||
setEppInput("domain_create_fee_refundable.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
void testFailure_refundableFee_std_v1() {
|
||||
setEppInput("domain_create_fee_refundable.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v11() {
|
||||
setEppInput("domain_create_fee_refundable.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
void testFailure_gracePeriodFee_std_v1() {
|
||||
setEppInput("domain_create_fee_grace_period.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v12() {
|
||||
setEppInput("domain_create_fee_refundable.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v06() {
|
||||
setEppInput("domain_create_fee_grace_period.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v11() {
|
||||
setEppInput("domain_create_fee_grace_period.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v12() {
|
||||
setEppInput("domain_create_fee_grace_period.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v06() {
|
||||
setEppInput("domain_create_fee_applied.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v11() {
|
||||
setEppInput("domain_create_fee_applied.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v12() {
|
||||
setEppInput("domain_create_fee_applied.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
void testFailure_appliedFee_std_v1() {
|
||||
setEppInput("domain_create_fee_applied.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -796,9 +712,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_premiumAndEap() throws Exception {
|
||||
void testSuccess_premiumAndEap_std_v1() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput("domain_create_premium_eap.xml");
|
||||
setEppInput("domain_create_premium_eap.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts("net");
|
||||
persistResource(
|
||||
Tld.get("example")
|
||||
@@ -816,8 +732,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE,
|
||||
UserPrivileges.NORMAL,
|
||||
loadFile(
|
||||
"domain_create_response_premium_eap.xml", ImmutableMap.of("DOMAIN", "rich.example")));
|
||||
loadFile("domain_create_response_premium_eap.xml", FEE_STD_1_0_MAP));
|
||||
assertSuccessfulCreate("example", ImmutableSet.of(), 200);
|
||||
assertNoLordn();
|
||||
}
|
||||
@@ -1047,8 +962,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v06() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
|
||||
void testFailure_wrongFeeAmount_std_v1() {
|
||||
setEppInput("domain_create_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
@@ -1061,7 +976,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v06() throws Exception {
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_std_v1() throws Exception {
|
||||
setupDefaultTokenWithDiscount();
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
@@ -1069,65 +984,14 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
.setCreateBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 8)))
|
||||
.build());
|
||||
// Expects fee of $24
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
// $15 is 50% off the first year registration ($8) and 0% 0ff the 2nd year (renewal at $11)
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.6", "FEE", "15.00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v06() throws Exception {
|
||||
setupDefaultTokenWithDiscount();
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCreateBillingCostTransitions(
|
||||
ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 100)))
|
||||
.build());
|
||||
// Expects fee of $24
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v11() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCreateBillingCostTransitions(
|
||||
ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v11() throws Exception {
|
||||
setupDefaultTokenWithDiscount();
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCreateBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 8)))
|
||||
.build());
|
||||
// Expects fee of $24
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
setEppInput("domain_create_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
// $12 is equal to 50% off the first year registration and 0% 0ff the 2nd year
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.11", "FEE", "15.00")));
|
||||
runFlowAssertResponse(loadFile("domain_create_response_fee.xml", FEE_STD_1_0_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v11() throws Exception {
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_std_v1() throws Exception {
|
||||
setupDefaultTokenWithDiscount();
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
@@ -1136,79 +1000,16 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 100)))
|
||||
.build());
|
||||
// Expects fee of $24
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
setEppInput("domain_create_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v12() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCreateBillingCostTransitions(
|
||||
ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v12() throws Exception {
|
||||
setupDefaultTokenWithDiscount();
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCreateBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 8)))
|
||||
.build());
|
||||
// Expects fee of $24
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
// $12 is equal to 50% off the first year registration and 0% 0ff the 2nd year
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "15.00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v12() throws Exception {
|
||||
setupDefaultTokenWithDiscount();
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCreateBillingCostTransitions(
|
||||
ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 100)))
|
||||
.build());
|
||||
// Expects fee of $24
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v06() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "EUR"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v11() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "EUR"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v12() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "EUR"));
|
||||
void testFailure_wrongCurrency_std_v1() {
|
||||
setEppInput(
|
||||
"domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "CURRENCY", "EUR"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -1216,7 +1017,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
|
||||
@Test
|
||||
void testFailure_unknownCurrency() {
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "BAD"));
|
||||
setEppInput(
|
||||
"domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "CURRENCY", "BAD"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UnknownCurrencyEppException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -1580,7 +1382,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_allocationToken_multiYearDiscount_worksForPremiums() throws Exception {
|
||||
void testSuccess_allocationToken_multiYearDiscount_worksForPremiums_std_v1() throws Exception {
|
||||
createTld("example");
|
||||
persistContactsAndHosts();
|
||||
persistResource(
|
||||
@@ -1601,11 +1403,17 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
clock.advanceOneMilli();
|
||||
setEppInput(
|
||||
"domain_create_premium_allocationtoken.xml",
|
||||
ImmutableMap.of("YEARS", "3", "FEE", "104.00"));
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "YEARS", "3", "FEE", "104.00"));
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_premium.xml",
|
||||
ImmutableMap.of("EXDATE", "2002-04-03T22:00:00.0Z", "FEE", "104.00")));
|
||||
ImmutableMap.of(
|
||||
"FEE_VERSION",
|
||||
"epp:fee-1.0",
|
||||
"EXDATE",
|
||||
"2002-04-03T22:00:00.0Z",
|
||||
"FEE",
|
||||
"104.00")));
|
||||
BillingEvent billingEvent =
|
||||
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.class));
|
||||
assertThat(billingEvent.getTargetId()).isEqualTo("rich.example");
|
||||
@@ -1614,7 +1422,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_allocationToken_singleYearDiscount_worksForPremiums() throws Exception {
|
||||
void testSuccess_allocationToken_singleYearDiscount_worksForPremiums_std_v1() throws Exception {
|
||||
createTld("example");
|
||||
persistContactsAndHosts();
|
||||
persistResource(
|
||||
@@ -1634,11 +1442,17 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
clock.advanceOneMilli();
|
||||
setEppInput(
|
||||
"domain_create_premium_allocationtoken.xml",
|
||||
ImmutableMap.of("YEARS", "3", "FEE", "204.44"));
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "YEARS", "3", "FEE", "204.44"));
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_premium.xml",
|
||||
ImmutableMap.of("EXDATE", "2002-04-03T22:00:00.0Z", "FEE", "204.44")));
|
||||
ImmutableMap.of(
|
||||
"FEE_VERSION",
|
||||
"epp:fee-1.0",
|
||||
"EXDATE",
|
||||
"2002-04-03T22:00:00.0Z",
|
||||
"FEE",
|
||||
"204.44")));
|
||||
BillingEvent billingEvent =
|
||||
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.class));
|
||||
assertThat(billingEvent.getTargetId()).isEqualTo("rich.example");
|
||||
@@ -1797,7 +1611,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_doesNotApplyNonPremiumDefaultTokenToPremiumName() throws Exception {
|
||||
void testSuccess_doesNotApplyNonPremiumDefaultTokenToPremiumName_std_v1() throws Exception {
|
||||
persistContactsAndHosts();
|
||||
createTld("example");
|
||||
persistResource(
|
||||
@@ -1805,11 +1619,17 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
.asBuilder()
|
||||
.setAllowedTlds(ImmutableSet.of("example"))
|
||||
.build());
|
||||
setEppInput("domain_create_premium.xml");
|
||||
setEppInput("domain_create_premium.xml", FEE_STD_1_0_MAP);
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_premium.xml",
|
||||
ImmutableMap.of("EXDATE", "2001-04-03T22:00:00.0Z", "FEE", "200.00")));
|
||||
ImmutableMap.of(
|
||||
"FEE_VERSION",
|
||||
"epp:fee-1.0",
|
||||
"EXDATE",
|
||||
"2001-04-03T22:00:00.0Z",
|
||||
"FEE",
|
||||
"200.00")));
|
||||
assertSuccessfulCreate("example", ImmutableSet.of(), 200);
|
||||
}
|
||||
|
||||
@@ -1995,9 +1815,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_superuserOverridesPremiumNameBlock() throws Exception {
|
||||
void testSuccess_superuserOverridesPremiumNameBlock_std_v1() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput("domain_create_premium.xml");
|
||||
setEppInput("domain_create_premium.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts("net");
|
||||
// Modify the Registrar to block premium names.
|
||||
persistResource(loadRegistrar("TheRegistrar").asBuilder().setBlockPremiumNames(true).build());
|
||||
@@ -2006,7 +1826,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
SUPERUSER,
|
||||
loadFile(
|
||||
"domain_create_response_premium.xml",
|
||||
ImmutableMap.of("EXDATE", "2001-04-03T22:00:00.0Z", "FEE", "200.00")));
|
||||
ImmutableMap.of(
|
||||
"FEE_VERSION",
|
||||
"epp:fee-1.0",
|
||||
"EXDATE",
|
||||
"2001-04-03T22:00:00.0Z",
|
||||
"FEE",
|
||||
"200.00")));
|
||||
assertSuccessfulCreate("example", ImmutableSet.of(), 200);
|
||||
}
|
||||
|
||||
@@ -2137,9 +1963,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_premiumBlocked() {
|
||||
void testFailure_premiumBlocked_std_v1() {
|
||||
createTld("example");
|
||||
setEppInput("domain_create_premium.xml");
|
||||
setEppInput("domain_create_premium.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts("net");
|
||||
// Modify the Registrar to block premium names.
|
||||
persistResource(loadRegistrar("TheRegistrar").asBuilder().setBlockPremiumNames(true).build());
|
||||
@@ -2157,60 +1983,20 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_omitFeeExtensionOnLogin_v06() {
|
||||
void testFailure_omitFeeExtensionOnLogin_std_v1() {
|
||||
for (String uri : FEE_EXTENSION_URIS) {
|
||||
removeServiceExtensionUri(uri);
|
||||
}
|
||||
createTld("net");
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6", "CURRENCY", "USD"));
|
||||
setEppInput("domain_create_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UndeclaredServiceExtensionException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_omitFeeExtensionOnLogin_v11() {
|
||||
for (String uri : FEE_EXTENSION_URIS) {
|
||||
removeServiceExtensionUri(uri);
|
||||
}
|
||||
createTld("net");
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UndeclaredServiceExtensionException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_omitFeeExtensionOnLogin_v12() {
|
||||
for (String uri : FEE_EXTENSION_URIS) {
|
||||
removeServiceExtensionUri(uri);
|
||||
}
|
||||
createTld("net");
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(UndeclaredServiceExtensionException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v06() {
|
||||
setEppInput("domain_create_fee_bad_scale.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v11() {
|
||||
setEppInput("domain_create_fee_bad_scale.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v12() {
|
||||
setEppInput("domain_create_fee_bad_scale.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
void testFailure_feeGivenInWrongScale_std_v1() {
|
||||
setEppInput("domain_create_fee_bad_scale.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -2699,8 +2485,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_eapFee_combined() {
|
||||
setEppInput("domain_create_eap_combined_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
void testFailure_eapFee_combined_std_v1() {
|
||||
setEppInput("domain_create_eap_combined_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
setEapForTld("tld");
|
||||
EppException thrown = assertThrows(FeeDescriptionParseException.class, this::runFlow);
|
||||
@@ -2709,12 +2495,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_eapFee_description_swapped() {
|
||||
void testFailure_eapFee_description_swapped_std_v1() {
|
||||
setEppInput(
|
||||
"domain_create_eap_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"FEE_VERSION",
|
||||
"0.6",
|
||||
"epp:fee-1.0",
|
||||
"DESCRIPTION_1",
|
||||
"Early Access Period",
|
||||
"DESCRIPTION_2",
|
||||
@@ -2727,11 +2513,11 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_eapFee_totalAmountNotMatched() {
|
||||
void testFailure_eapFee_totalAmountNotMatched_std_v1() {
|
||||
setEppInput(
|
||||
"domain_create_extra_fees.xml",
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.put("FEE_VERSION", "0.6")
|
||||
.put("FEE_VERSION", "epp:fee-1.0")
|
||||
.put("DESCRIPTION_1", "create")
|
||||
.put("FEE_1", "24")
|
||||
.put("DESCRIPTION_2", "Early Access Period")
|
||||
@@ -2747,11 +2533,11 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_eapFee_multipleEAPfees_doNotAddToExpectedValue() {
|
||||
void testSuccess_eapFee_multipleEAPfees_doNotAddToExpectedValue_std_v1() {
|
||||
setEppInput(
|
||||
"domain_create_extra_fees.xml",
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.put("FEE_VERSION", "0.6")
|
||||
.put("FEE_VERSION", "epp:fee-1.0")
|
||||
.put("DESCRIPTION_1", "create")
|
||||
.put("FEE_1", "24")
|
||||
.put("DESCRIPTION_2", "Early Access Period")
|
||||
@@ -2767,11 +2553,11 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_eapFee_multipleEAPfees_addToExpectedValue() throws Exception {
|
||||
void testSuccess_eapFee_multipleEAPfees_addToExpectedValue_std_v1() throws Exception {
|
||||
setEppInput(
|
||||
"domain_create_extra_fees.xml",
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.put("FEE_VERSION", "0.6")
|
||||
.put("FEE_VERSION", "epp:fee-1.0")
|
||||
.put("DESCRIPTION_1", "create")
|
||||
.put("FEE_1", "24")
|
||||
.put("DESCRIPTION_2", "Early Access Period")
|
||||
@@ -2781,33 +2567,36 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
.build());
|
||||
persistContactsAndHosts();
|
||||
setEapForTld("tld");
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_eap_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
doSuccessfulTest("tld", "domain_create_response_eap_fee.xml", FEE_STD_1_0_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_eapFee_fullDescription_includingArbitraryExpiryTime() throws Exception {
|
||||
void testSuccess_eapFee_fullDescription_includingArbitraryExpiryTime_std_v1() throws Exception {
|
||||
setEppInput(
|
||||
"domain_create_eap_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"FEE_VERSION",
|
||||
"0.6",
|
||||
"epp:fee-1.0",
|
||||
"DESCRIPTION_1",
|
||||
"create",
|
||||
"DESCRIPTION_2",
|
||||
"Early Access Period, fee expires: 2022-03-01T00:00:00.000Z"));
|
||||
persistContactsAndHosts();
|
||||
setEapForTld("tld");
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_eap_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
doSuccessfulTest("tld", "domain_create_response_eap_fee.xml", FEE_STD_1_0_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_eapFee_description_multipleMatch() {
|
||||
void testFailure_eapFee_description_multipleMatch_std_v1() {
|
||||
setEppInput(
|
||||
"domain_create_eap_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"FEE_VERSION", "0.6", "DESCRIPTION_1", "create", "DESCRIPTION_2", "renew transfer"));
|
||||
"FEE_VERSION",
|
||||
"epp:fee-1.0",
|
||||
"DESCRIPTION_1",
|
||||
"create",
|
||||
"DESCRIPTION_2",
|
||||
"renew transfer"));
|
||||
persistContactsAndHosts();
|
||||
setEapForTld("tld");
|
||||
EppException thrown = assertThrows(FeeDescriptionMultipleMatchesException.class, this::runFlow);
|
||||
@@ -2816,54 +2605,17 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_eapFeeApplied_v06() throws Exception {
|
||||
void testSuccess_eapFeeApplied_std_v1() throws Exception {
|
||||
setEppInput(
|
||||
"domain_create_eap_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"FEE_VERSION",
|
||||
"0.6",
|
||||
"DESCRIPTION_1",
|
||||
"create",
|
||||
"DESCRIPTION_2",
|
||||
"Early Access Period"));
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.putAll(FEE_STD_1_0_MAP)
|
||||
.put("DESCRIPTION_1", "create")
|
||||
.put("DESCRIPTION_2", "Early Access Period")
|
||||
.build());
|
||||
persistContactsAndHosts();
|
||||
setEapForTld("tld");
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_eap_fee.xml", ImmutableMap.of("FEE_VERSION", "0.6"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_eapFeeApplied_v11() throws Exception {
|
||||
setEppInput(
|
||||
"domain_create_eap_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"FEE_VERSION",
|
||||
"0.11",
|
||||
"DESCRIPTION_1",
|
||||
"create",
|
||||
"DESCRIPTION_2",
|
||||
"Early Access Period"));
|
||||
persistContactsAndHosts();
|
||||
setEapForTld("tld");
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_eap_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_eapFeeApplied_v12() throws Exception {
|
||||
setEppInput(
|
||||
"domain_create_eap_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"FEE_VERSION",
|
||||
"0.12",
|
||||
"DESCRIPTION_1",
|
||||
"create",
|
||||
"DESCRIPTION_2",
|
||||
"Early Access Period"));
|
||||
persistContactsAndHosts();
|
||||
setEapForTld("tld");
|
||||
doSuccessfulTest(
|
||||
"tld", "domain_create_response_eap_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12"));
|
||||
doSuccessfulTest("tld", "domain_create_response_eap_fee.xml", FEE_STD_1_0_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -3002,7 +2754,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_nonAnchorTenant_nonPremiumRenewal() throws Exception {
|
||||
void testSuccess_nonAnchorTenant_nonPremiumRenewal_std_v1() throws Exception {
|
||||
createTld("example");
|
||||
AllocationToken token =
|
||||
persistResource(
|
||||
@@ -3016,13 +2768,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
// Creation is still $100 but it'll create a NONPREMIUM renewal
|
||||
setEppInput(
|
||||
"domain_create_premium_allocationtoken.xml",
|
||||
ImmutableMap.of("YEARS", "2", "FEE", "111.00"));
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "YEARS", "2", "FEE", "111.00"));
|
||||
runFlow();
|
||||
assertSuccessfulCreate("example", ImmutableSet.of(), token, 111);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_specifiedRenewalPriceToken_specifiedRecurrencePrice() throws Exception {
|
||||
void testSuccess_specifiedRenewalPriceToken_specifiedRecurrencePrice_std_v1() throws Exception {
|
||||
createTld("example");
|
||||
AllocationToken token =
|
||||
persistResource(
|
||||
@@ -3037,7 +2789,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
// Creation is still $100 but it'll create a $1 renewal
|
||||
setEppInput(
|
||||
"domain_create_premium_allocationtoken.xml",
|
||||
ImmutableMap.of("YEARS", "2", "FEE", "101.00"));
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "YEARS", "2", "FEE", "101.00"));
|
||||
runFlow();
|
||||
assertSuccessfulCreate("example", ImmutableSet.of(), token, 101, 1);
|
||||
}
|
||||
@@ -3087,7 +2839,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_nonpremiumCreateToken() throws Exception {
|
||||
void testSuccess_nonpremiumCreateToken_std_v1() throws Exception {
|
||||
createTld("example");
|
||||
persistContactsAndHosts();
|
||||
persistResource(
|
||||
@@ -3098,8 +2850,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
.setDomainName("rich.example")
|
||||
.build());
|
||||
setEppInput(
|
||||
"domain_create_premium_allocationtoken.xml", ImmutableMap.of("YEARS", "1", "FEE", "13.00"));
|
||||
runFlowAssertResponse(loadFile("domain_create_nonpremium_token_response.xml"));
|
||||
"domain_create_premium_allocationtoken.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "YEARS", "1", "FEE", "13.00"));
|
||||
runFlowAssertResponse(loadFile("domain_create_nonpremium_token_response.xml", FEE_STD_1_0_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -3495,7 +3248,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
void testTieredPricingPromoResponse() throws Exception {
|
||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||
setupDefaultTokenWithDiscount("NewRegistrar");
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
setEppInput("domain_create_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
|
||||
// Fee in the result should be 24 (create cost of 13 plus renew cost of 11) even though the
|
||||
@@ -3503,29 +3256,29 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "24.00")));
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "FEE", "24.00")));
|
||||
// Expected cost is half off the create cost (13/2 == 6.50) plus one full-cost renew (11)
|
||||
assertThat(Iterables.getOnlyElement(loadAllOf(BillingEvent.class)).getCost())
|
||||
.isEqualTo(Money.of(USD, 17.50));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTieredPricingPromo_registrarNotIncluded_standardResponse() throws Exception {
|
||||
void testTieredPricingPromo_registrarNotIncluded_standardResponse_std_v1() throws Exception {
|
||||
setupDefaultTokenWithDiscount("NewRegistrar");
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
setEppInput("domain_create_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
|
||||
// For a registrar not included in the tiered pricing promo, costs should be 24
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "24.00")));
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "FEE", "24.00")));
|
||||
assertThat(Iterables.getOnlyElement(loadAllOf(BillingEvent.class)).getCost())
|
||||
.isEqualTo(Money.of(USD, 24));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTieredPricingPromo_registrarIncluded_noTokenActive() throws Exception {
|
||||
void testTieredPricingPromo_registrarIncluded_noTokenActive_std_v1() throws Exception {
|
||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||
persistActiveDomain("example1.tld");
|
||||
|
||||
@@ -3540,14 +3293,14 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||
TokenStatus.VALID))
|
||||
.build());
|
||||
|
||||
setEppInput("domain_create_fee.xml", ImmutableMap.of("FEE_VERSION", "0.12", "CURRENCY", "USD"));
|
||||
setEppInput("domain_create_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistContactsAndHosts();
|
||||
|
||||
// The token hasn't started yet, so the cost should be create (13) plus renew (11)
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_create_response_fee.xml",
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE", "24.00")));
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "FEE", "24.00")));
|
||||
assertThat(Iterables.getOnlyElement(loadAllOf(BillingEvent.class)).getCost())
|
||||
.isEqualTo(Money.of(USD, 24));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows.domain;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_DELETE;
|
||||
import static google.registry.testing.DatabaseHelper.assertBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.assertDomainDnsRequests;
|
||||
import static google.registry.testing.DatabaseHelper.assertPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
|
||||
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
|
||||
import static google.registry.testing.DatabaseHelper.getPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.loadByKey;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DomainSubject.assertAboutDomains;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.model.billing.BillingBase.Flag;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingCancellation;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Tests for {@link DomainDeleteFlow} that use the old fee extensions (0.6, 0.11, 0.12). */
|
||||
public class DomainDeleteFlowOldFeeExtensionsTest
|
||||
extends ProductionSimulatingFeeExtensionsTest<DomainDeleteFlow> {
|
||||
|
||||
private static final DateTime TIME_BEFORE_FLOW = DateTime.parse("2000-06-06T22:00:00.0Z");
|
||||
private static final DateTime A_MONTH_AGO = TIME_BEFORE_FLOW.minusMonths(1);
|
||||
private static final DateTime A_MONTH_FROM_NOW = TIME_BEFORE_FLOW.plusMonths(1);
|
||||
|
||||
private static final ImmutableMap<String, String> FEE_06_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "fee-0.6", "FEE_NS", "fee");
|
||||
private static final ImmutableMap<String, String> FEE_11_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "fee-0.11", "FEE_NS", "fee11");
|
||||
private static final ImmutableMap<String, String> FEE_12_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "fee-0.12", "FEE_NS", "fee12");
|
||||
|
||||
private Domain domain;
|
||||
private DomainHistory earlierHistoryEntry;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEachomainDeleteFlowOldFeeExtensionsTest() {
|
||||
clock.setTo(TIME_BEFORE_FLOW);
|
||||
setEppInput("domain_delete.xml");
|
||||
createTld("tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_renewGracePeriodCredit_v06() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_11.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
doSuccessfulTest_noAddGracePeriod("domain_delete_response_pending_fee.xml", FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_renewGracePeriodCredit_v11() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
doSuccessfulTest_noAddGracePeriod("domain_delete_response_pending_fee.xml", FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_renewGracePeriodCredit_v12() throws Exception {
|
||||
doSuccessfulTest_noAddGracePeriod("domain_delete_response_pending_fee.xml", FEE_12_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_addGracePeriodCredit_v06() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_11.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
doAddGracePeriodDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_addGracePeriodCredit_v11() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
doAddGracePeriodDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_addGracePeriodCredit_v12() throws Exception {
|
||||
doAddGracePeriodDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_12_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_v06() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_11.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_06_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_v11() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_11_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_v12() throws Exception {
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_12_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_priceChanges_v06() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_11.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setRenewBillingCostTransitions(
|
||||
ImmutableSortedMap.of(
|
||||
START_OF_TIME,
|
||||
Money.of(USD, 11),
|
||||
TIME_BEFORE_FLOW.minusDays(5),
|
||||
Money.of(USD, 20)))
|
||||
.build());
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_06_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_priceChanges_v11() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setRenewBillingCostTransitions(
|
||||
ImmutableSortedMap.of(
|
||||
START_OF_TIME,
|
||||
Money.of(USD, 11),
|
||||
TIME_BEFORE_FLOW.minusDays(5),
|
||||
Money.of(USD, 20)))
|
||||
.build());
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_11_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_priceChanges_v12() throws Exception {
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setRenewBillingCostTransitions(
|
||||
ImmutableSortedMap.of(
|
||||
START_OF_TIME,
|
||||
Money.of(USD, 11),
|
||||
TIME_BEFORE_FLOW.minusDays(5),
|
||||
Money.of(USD, 20)))
|
||||
.build());
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_12_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_freeCreation_deletionDuringGracePeriod_v12() throws Exception {
|
||||
// Deletion during the add grace period should still work even if the credit is 0
|
||||
setUpSuccessfulTest();
|
||||
BillingEvent graceBillingEvent =
|
||||
persistResource(createBillingEvent(Reason.CREATE, Money.of(USD, 0)));
|
||||
setUpGracePeriods(
|
||||
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, domain.getRepoId(), graceBillingEvent));
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_fee_free_grace_v12.xml"));
|
||||
}
|
||||
|
||||
private void createReferencedEntities(DateTime expirationTime) throws Exception {
|
||||
// Persist a linked contact.
|
||||
Contact contact = persistActiveContact("sh8013");
|
||||
domain =
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(TIME_BEFORE_FLOW)
|
||||
.setRegistrant(Optional.of(contact.createVKey()))
|
||||
.setRegistrationExpirationTime(expirationTime)
|
||||
.build());
|
||||
earlierHistoryEntry =
|
||||
persistResource(
|
||||
new DomainHistory.Builder()
|
||||
.setType(DOMAIN_CREATE)
|
||||
.setDomain(domain)
|
||||
.setModificationTime(clock.nowUtc())
|
||||
.setRegistrarId(domain.getCreationRegistrarId())
|
||||
.build());
|
||||
}
|
||||
|
||||
private void setUpSuccessfulTest() throws Exception {
|
||||
createReferencedEntities(A_MONTH_FROM_NOW);
|
||||
BillingRecurrence autorenewBillingEvent =
|
||||
persistResource(createAutorenewBillingEvent("TheRegistrar").build());
|
||||
PollMessage.Autorenew autorenewPollMessage =
|
||||
persistResource(createAutorenewPollMessage("TheRegistrar").build());
|
||||
|
||||
domain =
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setAutorenewBillingEvent(autorenewBillingEvent.createVKey())
|
||||
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
|
||||
.build());
|
||||
|
||||
assertMutatingFlow(true);
|
||||
}
|
||||
|
||||
private void doSuccessfulTest_noAddGracePeriod(String responseFilename) throws Exception {
|
||||
doSuccessfulTest_noAddGracePeriod(responseFilename, ImmutableMap.of());
|
||||
}
|
||||
|
||||
private void doSuccessfulTest_noAddGracePeriod(
|
||||
String responseFilename, Map<String, String> substitutions) throws Exception {
|
||||
// Persist the billing event so it can be retrieved for cancellation generation and checking.
|
||||
setUpSuccessfulTest();
|
||||
BillingEvent renewBillingEvent =
|
||||
persistResource(createBillingEvent(Reason.RENEW, Money.of(USD, 456)));
|
||||
setUpGracePeriods(
|
||||
GracePeriod.forBillingEvent(GracePeriodStatus.RENEW, domain.getRepoId(), renewBillingEvent),
|
||||
// This grace period has no associated billing event, so it won't cause a cancellation.
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.TRANSFER,
|
||||
domain.getRepoId(),
|
||||
TIME_BEFORE_FLOW.plusDays(1),
|
||||
"NewRegistrar",
|
||||
null));
|
||||
// We should see exactly one poll message, which is for the autorenew 1 month in the future.
|
||||
assertPollMessages(createAutorenewPollMessage("TheRegistrar").build());
|
||||
DateTime expectedExpirationTime = domain.getRegistrationExpirationTime().minusYears(2);
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile(responseFilename, substitutions));
|
||||
Domain resource = reloadResourceByForeignKey();
|
||||
// Check that the domain is in the pending delete state.
|
||||
assertAboutDomains()
|
||||
.that(resource)
|
||||
.hasStatusValue(StatusValue.PENDING_DELETE)
|
||||
.and()
|
||||
.hasDeletionTime(
|
||||
clock
|
||||
.nowUtc()
|
||||
.plus(Tld.get("tld").getRedemptionGracePeriodLength())
|
||||
.plus(Tld.get("tld").getPendingDeleteLength()))
|
||||
.and()
|
||||
.hasExactlyStatusValues(StatusValue.INACTIVE, StatusValue.PENDING_DELETE)
|
||||
.and()
|
||||
.hasOneHistoryEntryEachOfTypes(DOMAIN_CREATE, DOMAIN_DELETE);
|
||||
// We leave the original expiration time unchanged; if the expiration time is before the
|
||||
// deletion time, that means once it passes the domain will experience a "phantom autorenew"
|
||||
// where the expirationTime advances and the grace period appears, but since the delete flow
|
||||
// closed the autorenew recurrences immediately, there are no other autorenew effects.
|
||||
assertAboutDomains().that(resource).hasRegistrationExpirationTime(expectedExpirationTime);
|
||||
assertLastHistoryContainsResource(resource);
|
||||
// All existing grace periods that were for billable actions should cause cancellations.
|
||||
assertAutorenewClosedAndCancellationCreatedFor(
|
||||
renewBillingEvent, getOnlyHistoryEntryOfType(resource, DOMAIN_DELETE, DomainHistory.class));
|
||||
// All existing grace periods should be gone, and a new REDEMPTION one should be added.
|
||||
assertThat(resource.getGracePeriods())
|
||||
.containsExactly(
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.REDEMPTION,
|
||||
domain.getRepoId(),
|
||||
clock.nowUtc().plus(Tld.get("tld").getRedemptionGracePeriodLength()),
|
||||
"TheRegistrar",
|
||||
null,
|
||||
resource.getGracePeriods().iterator().next().getGracePeriodId()));
|
||||
assertDeletionPollMessageFor(resource, "Domain deleted.");
|
||||
}
|
||||
|
||||
private void assertDeletionPollMessageFor(Domain domain, String expectedMessage) {
|
||||
// There should be a future poll message at the deletion time. The previous autorenew poll
|
||||
// message should now be deleted.
|
||||
assertAboutDomains().that(domain).hasDeletePollMessage();
|
||||
DateTime deletionTime = domain.getDeletionTime();
|
||||
assertThat(getPollMessages("TheRegistrar", deletionTime.minusMinutes(1))).isEmpty();
|
||||
assertThat(getPollMessages("TheRegistrar", deletionTime)).hasSize(1);
|
||||
assertThat(domain.getDeletePollMessage())
|
||||
.isEqualTo(getOnlyPollMessage("TheRegistrar").createVKey());
|
||||
PollMessage.OneTime deletePollMessage = loadByKey(domain.getDeletePollMessage());
|
||||
assertThat(deletePollMessage.getMsg()).isEqualTo(expectedMessage);
|
||||
}
|
||||
|
||||
private void setUpGracePeriods(GracePeriod... gracePeriods) {
|
||||
domain =
|
||||
persistResource(
|
||||
domain.asBuilder().setGracePeriods(ImmutableSet.copyOf(gracePeriods)).build());
|
||||
}
|
||||
|
||||
private void assertAutorenewClosedAndCancellationCreatedFor(
|
||||
BillingEvent graceBillingEvent, DomainHistory historyEntryDomainDelete) {
|
||||
assertAutorenewClosedAndCancellationCreatedFor(
|
||||
graceBillingEvent, historyEntryDomainDelete, clock.nowUtc());
|
||||
}
|
||||
|
||||
private void assertAutorenewClosedAndCancellationCreatedFor(
|
||||
BillingEvent graceBillingEvent, DomainHistory historyEntryDomainDelete, DateTime eventTime) {
|
||||
assertBillingEvents(
|
||||
createAutorenewBillingEvent("TheRegistrar").setRecurrenceEndTime(eventTime).build(),
|
||||
graceBillingEvent,
|
||||
new BillingCancellation.Builder()
|
||||
.setReason(graceBillingEvent.getReason())
|
||||
.setTargetId("example.tld")
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(eventTime)
|
||||
.setBillingTime(TIME_BEFORE_FLOW.plusDays(1))
|
||||
.setBillingEvent(graceBillingEvent.createVKey())
|
||||
.setDomainHistory(historyEntryDomainDelete)
|
||||
.build());
|
||||
}
|
||||
|
||||
private BillingRecurrence.Builder createAutorenewBillingEvent(String registrarId) {
|
||||
return new BillingRecurrence.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
.setTargetId("example.tld")
|
||||
.setRegistrarId(registrarId)
|
||||
.setEventTime(A_MONTH_FROM_NOW)
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setDomainHistory(earlierHistoryEntry);
|
||||
}
|
||||
|
||||
private PollMessage.Autorenew.Builder createAutorenewPollMessage(String registrarId) {
|
||||
return new PollMessage.Autorenew.Builder()
|
||||
.setTargetId("example.tld")
|
||||
.setRegistrarId(registrarId)
|
||||
.setEventTime(A_MONTH_FROM_NOW)
|
||||
.setAutorenewEndTime(END_OF_TIME)
|
||||
.setHistoryEntry(earlierHistoryEntry);
|
||||
}
|
||||
|
||||
private BillingEvent createBillingEvent(Reason reason, Money cost) {
|
||||
return new BillingEvent.Builder()
|
||||
.setReason(reason)
|
||||
.setTargetId("example.tld")
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setCost(cost)
|
||||
.setPeriodYears(2)
|
||||
.setEventTime(TIME_BEFORE_FLOW.minusDays(4))
|
||||
.setBillingTime(TIME_BEFORE_FLOW.plusDays(1))
|
||||
.setDomainHistory(earlierHistoryEntry)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void doAddGracePeriodDeleteTest(
|
||||
GracePeriodStatus gracePeriodStatus, String responseFilename) throws Exception {
|
||||
doAddGracePeriodDeleteTest(gracePeriodStatus, responseFilename, ImmutableMap.of());
|
||||
}
|
||||
|
||||
private void doAddGracePeriodDeleteTest(
|
||||
GracePeriodStatus gracePeriodStatus,
|
||||
String responseFilename,
|
||||
Map<String, String> substitutions)
|
||||
throws Exception {
|
||||
// Persist the billing event so it can be retrieved for cancellation generation and checking.
|
||||
setUpSuccessfulTest();
|
||||
BillingEvent graceBillingEvent =
|
||||
persistResource(createBillingEvent(Reason.CREATE, Money.of(USD, 123)));
|
||||
setUpGracePeriods(
|
||||
GracePeriod.forBillingEvent(gracePeriodStatus, domain.getRepoId(), graceBillingEvent));
|
||||
// We should see exactly one poll message, which is for the autorenew 1 month in the future.
|
||||
assertPollMessages(createAutorenewPollMessage("TheRegistrar").build());
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile(responseFilename, substitutions));
|
||||
// Check that the domain is fully deleted.
|
||||
assertThat(reloadResourceByForeignKey()).isNull();
|
||||
// The add grace period is for a billable action, so it should trigger a cancellation.
|
||||
assertAutorenewClosedAndCancellationCreatedFor(
|
||||
graceBillingEvent, getOnlyHistoryEntryOfType(domain, DOMAIN_DELETE, DomainHistory.class));
|
||||
assertDomainDnsRequests("example.tld");
|
||||
// There should be no poll messages. The previous autorenew poll message should now be deleted.
|
||||
assertThat(getPollMessages("TheRegistrar")).isEmpty();
|
||||
}
|
||||
|
||||
private void setUpAutorenewGracePeriod() throws Exception {
|
||||
createReferencedEntities(A_MONTH_AGO.plusYears(1));
|
||||
BillingRecurrence autorenewBillingEvent =
|
||||
persistResource(
|
||||
createAutorenewBillingEvent("TheRegistrar").setEventTime(A_MONTH_AGO).build());
|
||||
PollMessage.Autorenew autorenewPollMessage =
|
||||
persistResource(
|
||||
createAutorenewPollMessage("TheRegistrar").setEventTime(A_MONTH_AGO).build());
|
||||
domain =
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setGracePeriods(
|
||||
ImmutableSet.of(
|
||||
GracePeriod.createForRecurrence(
|
||||
GracePeriodStatus.AUTO_RENEW,
|
||||
domain.getRepoId(),
|
||||
A_MONTH_AGO.plusDays(45),
|
||||
"TheRegistrar",
|
||||
autorenewBillingEvent.createVKey())))
|
||||
.setAutorenewBillingEvent(autorenewBillingEvent.createVKey())
|
||||
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
|
||||
.build());
|
||||
assertMutatingFlow(true);
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,6 @@ import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.host.Host;
|
||||
@@ -122,12 +121,8 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
private static final DateTime A_MONTH_AGO = TIME_BEFORE_FLOW.minusMonths(1);
|
||||
private static final DateTime A_MONTH_FROM_NOW = TIME_BEFORE_FLOW.plusMonths(1);
|
||||
|
||||
private static final ImmutableMap<String, String> FEE_06_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "0.6", "FEE_NS", "fee");
|
||||
private static final ImmutableMap<String, String> FEE_11_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "0.11", "FEE_NS", "fee11");
|
||||
private static final ImmutableMap<String, String> FEE_12_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE_NS", "fee12");
|
||||
private static final ImmutableMap<String, String> FEE_STD_1_0_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "FEE_NS", "fee1_00");
|
||||
|
||||
DomainDeleteFlowTest() {
|
||||
setEppInput("domain_delete.xml");
|
||||
@@ -396,24 +391,9 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_addGracePeriodCredit_v06() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_11.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
doAddGracePeriodDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_addGracePeriodCredit_v11() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
doAddGracePeriodDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_addGracePeriodCredit_v12() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
doAddGracePeriodDeleteTest(GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_12_MAP);
|
||||
void testSuccess_addGracePeriodCredit_std_v1() throws Exception {
|
||||
doAddGracePeriodDeleteTest(
|
||||
GracePeriodStatus.ADD, "domain_delete_response_fee.xml", FEE_STD_1_0_MAP);
|
||||
}
|
||||
|
||||
private void doSuccessfulTest_noAddGracePeriod(String responseFilename) throws Exception {
|
||||
@@ -497,24 +477,8 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_renewGracePeriodCredit_v06() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_11.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
doSuccessfulTest_noAddGracePeriod("domain_delete_response_pending_fee.xml", FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_renewGracePeriodCredit_v11() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
doSuccessfulTest_noAddGracePeriod("domain_delete_response_pending_fee.xml", FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_renewGracePeriodCredit_v12() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
doSuccessfulTest_noAddGracePeriod("domain_delete_response_pending_fee.xml", FEE_12_MAP);
|
||||
void testSuccess_renewGracePeriodCredit_std_v1() throws Exception {
|
||||
doSuccessfulTest_noAddGracePeriod("domain_delete_response_pending_fee.xml", FEE_STD_1_0_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -556,37 +520,14 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_v06() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_11.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
void testSuccess_autoRenewGracePeriod_std_v1() throws Exception {
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_06_MAP));
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_STD_1_0_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_v11() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_11_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_v12() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_12_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_priceChanges_v06() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_11.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
void testSuccess_autoRenewGracePeriod_priceChanges_std_v1() throws Exception {
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
@@ -599,44 +540,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
.build());
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_06_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_priceChanges_v11() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_0_12.getUri());
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setRenewBillingCostTransitions(
|
||||
ImmutableSortedMap.of(
|
||||
START_OF_TIME,
|
||||
Money.of(USD, 11),
|
||||
TIME_BEFORE_FLOW.minusDays(5),
|
||||
Money.of(USD, 20)))
|
||||
.build());
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_11_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_autoRenewGracePeriod_priceChanges_v12() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setRenewBillingCostTransitions(
|
||||
ImmutableSortedMap.of(
|
||||
START_OF_TIME,
|
||||
Money.of(USD, 11),
|
||||
TIME_BEFORE_FLOW.minusDays(5),
|
||||
Money.of(USD, 20)))
|
||||
.build());
|
||||
setUpAutorenewGracePeriod();
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_12_MAP));
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_autorenew_fee.xml", FEE_STD_1_0_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1296,7 +1200,6 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
|
||||
@Test
|
||||
void testSuccess_freeCreation_deletionDuringGracePeriod() throws Exception {
|
||||
removeServiceExtensionUri(ServiceExtension.FEE_1_00.getUri());
|
||||
// Deletion during the add grace period should still work even if the credit is 0
|
||||
setUpSuccessfulTest();
|
||||
BillingEvent graceBillingEvent =
|
||||
@@ -1304,7 +1207,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
setUpGracePeriods(
|
||||
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, domain.getRepoId(), graceBillingEvent));
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_fee_free_grace.xml"));
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_fee_free_grace_stdv1.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,523 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows.domain;
|
||||
|
||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.DEFAULT;
|
||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.NONPREMIUM;
|
||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.SPECIFIED;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static google.registry.testing.DatabaseHelper.persistBillingRecurrenceForDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistPremiumList;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.testing.TestDataHelper.updateSubstitutions;
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.UnknownCurrencyEppException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.BadPeriodUnitException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.FeeChecksDontSupportPhasesException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.RestoresAreAlwaysForOneYearException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.TransfersAreAlwaysForOneYearException;
|
||||
import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainAuthInfo;
|
||||
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.persistence.transaction.JpaTransactionManagerExtension;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Tests for {@link DomainInfoFlow} that use the old fee extensions (0.6, 0.11, 0.12). */
|
||||
public class DomainInfoFlowOldFeeExtensionsTest
|
||||
extends ProductionSimulatingFeeExtensionsTest<DomainInfoFlow> {
|
||||
|
||||
/**
|
||||
* The domain_info_fee.xml default substitutions common to most tests.
|
||||
*
|
||||
* <p>It doesn't set a default value to the COMMAND and PERIOD keys, because they are different in
|
||||
* every test.
|
||||
*/
|
||||
private static final ImmutableMap<String, String> SUBSTITUTION_BASE =
|
||||
ImmutableMap.of(
|
||||
"NAME", "example.tld",
|
||||
"CURRENCY", "USD",
|
||||
"UNIT", "y");
|
||||
|
||||
private static final Pattern OK_PATTERN = Pattern.compile("\"ok\"");
|
||||
|
||||
private Host host1;
|
||||
private Host host2;
|
||||
private Host host3;
|
||||
private Domain domain;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEachDomainInfoFlowOldFeeExtensionsTest() {
|
||||
setEppInput("domain_info.xml");
|
||||
clock.setTo(DateTime.parse("2005-03-03T22:00:00.000Z"));
|
||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
JpaTransactionManagerExtension.makeRegistrar1()
|
||||
.asBuilder()
|
||||
.setRegistrarId("ClientZ")
|
||||
.build());
|
||||
}
|
||||
|
||||
private void persistTestEntities(String domainName, boolean inactive) {
|
||||
host1 = persistActiveHost("ns1.example.tld");
|
||||
host2 = persistActiveHost("ns1.example.net");
|
||||
domain =
|
||||
persistResource(
|
||||
new Domain.Builder()
|
||||
.setDomainName(domainName)
|
||||
.setRepoId("2FF-TLD")
|
||||
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
|
||||
.setCreationRegistrarId("TheRegistrar")
|
||||
.setLastEppUpdateRegistrarId("NewRegistrar")
|
||||
.setCreationTimeForTest(DateTime.parse("1999-04-03T22:00:00.0Z"))
|
||||
.setLastEppUpdateTime(DateTime.parse("1999-12-03T09:00:00.0Z"))
|
||||
.setLastTransferTime(DateTime.parse("2000-04-08T09:00:00.0Z"))
|
||||
.setRegistrationExpirationTime(DateTime.parse("2005-04-03T22:00:00.0Z"))
|
||||
.setNameservers(
|
||||
inactive ? null : ImmutableSet.of(host1.createVKey(), host2.createVKey()))
|
||||
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("2fooBAR")))
|
||||
.build());
|
||||
// Set the superordinate domain of ns1.example.com to example.com. In reality, this would have
|
||||
// happened in the flow that created it, but here we just overwrite it in the database.
|
||||
host1 = persistResource(host1.asBuilder().setSuperordinateDomain(domain.createVKey()).build());
|
||||
// Create a subordinate host that is not delegated to by anyone.
|
||||
host3 =
|
||||
persistResource(
|
||||
new Host.Builder()
|
||||
.setHostName("ns2.example.tld")
|
||||
.setRepoId("3FF-TLD")
|
||||
.setSuperordinateDomain(domain.createVKey())
|
||||
.build());
|
||||
// Add the subordinate host keys to the existing domain.
|
||||
domain =
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setSubordinateHosts(ImmutableSet.of(host1.getHostName(), host3.getHostName()))
|
||||
.build());
|
||||
}
|
||||
|
||||
private void persistTestEntities(boolean inactive) {
|
||||
persistTestEntities("example.tld", inactive);
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(
|
||||
String expectedXmlFilename,
|
||||
boolean inactive,
|
||||
ImmutableMap<String, String> substitutions,
|
||||
boolean expectHistoryAndBilling)
|
||||
throws Exception {
|
||||
assertMutatingFlow(true);
|
||||
String expected =
|
||||
loadFile(expectedXmlFilename, updateSubstitutions(substitutions, "ROID", "2FF-TLD"));
|
||||
if (inactive) {
|
||||
expected = OK_PATTERN.matcher(expected).replaceAll("\"inactive\"");
|
||||
}
|
||||
runFlowAssertResponse(expected);
|
||||
if (!expectHistoryAndBilling) {
|
||||
assertNoHistory();
|
||||
assertNoBillingEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(String expectedXmlFilename, boolean inactive) throws Exception {
|
||||
doSuccessfulTest(expectedXmlFilename, inactive, ImmutableMap.of(), false);
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(String expectedXmlFilename) throws Exception {
|
||||
persistTestEntities(false);
|
||||
doSuccessfulTest(expectedXmlFilename, false);
|
||||
}
|
||||
|
||||
private void doSuccessfulTestNoNameservers(String expectedXmlFilename) throws Exception {
|
||||
persistTestEntities(true);
|
||||
doSuccessfulTest(expectedXmlFilename, true);
|
||||
}
|
||||
|
||||
/** sets up a sample recurring billing event as part of the domain creation process. */
|
||||
private void setUpBillingEventForExistingDomain() {
|
||||
setUpBillingEventForExistingDomain(DEFAULT, null);
|
||||
}
|
||||
|
||||
private void setUpBillingEventForExistingDomain(
|
||||
RenewalPriceBehavior renewalPriceBehavior, @Nullable Money renewalPrice) {
|
||||
domain = persistBillingRecurrenceForDomain(domain, renewalPriceBehavior, renewalPrice);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_restoreCommand_pendingDelete_withRenewal() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "NAME", "rich.example", "COMMAND", "restore", "PERIOD", "1"));
|
||||
persistTestEntities("rich.example", false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setDeletionTime(clock.nowUtc().plusDays(25))
|
||||
.setRegistrationExpirationTime(clock.nowUtc().minusDays(1))
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.build());
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_restore_response_with_renewal.xml", false, ImmutableMap.of(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create command. Fee extension version 6 is the only one which supports fee extensions on
|
||||
* info commands and responses, so we don't need to test the other versions.
|
||||
*/
|
||||
@Test
|
||||
void testFeeExtension_createCommand() throws Exception {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "create", "PERIOD", "2"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of(
|
||||
"COMMAND", "create",
|
||||
"DESCRIPTION", "create",
|
||||
"PERIOD", "2",
|
||||
"FEE", "24.00"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test renew command. */
|
||||
@Test
|
||||
void testFeeExtension_renewCommand() throws Exception {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "2"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of(
|
||||
"COMMAND", "renew",
|
||||
"DESCRIPTION", "renew",
|
||||
"PERIOD", "2",
|
||||
"FEE", "22.00"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test transfer command. */
|
||||
@Test
|
||||
void testFeeExtension_transferCommand() throws Exception {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "transfer", "PERIOD", "1"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of(
|
||||
"COMMAND", "transfer",
|
||||
"DESCRIPTION", "renew",
|
||||
"PERIOD", "1",
|
||||
"FEE", "11.00"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test restore command. */
|
||||
@Test
|
||||
void testFeeExtension_restoreCommand() throws Exception {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "restore", "PERIOD", "1"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest("domain_info_fee_restore_response.xml", false, ImmutableMap.of(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_restoreCommand_pendingDelete_noRenewal() throws Exception {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "restore", "PERIOD", "1"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setDeletionTime(clock.nowUtc().plusDays(25))
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.build());
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_restore_response_no_renewal.xml", false, ImmutableMap.of(), true);
|
||||
}
|
||||
|
||||
/** Test create command on a premium label. */
|
||||
@Test
|
||||
void testFeeExtension_createCommandPremium() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "NAME", "rich.example", "COMMAND", "create", "PERIOD", "1"));
|
||||
persistTestEntities("rich.example", false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_premium_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "create", "DESCRIPTION", "create"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test renew command on a premium label. */
|
||||
@Test
|
||||
void testFeeExtension_renewCommandPremium() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "NAME", "rich.example", "COMMAND", "renew", "PERIOD", "1"));
|
||||
persistTestEntities("rich.example", false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_premium_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew"),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_renewCommandPremium_anchorTenant() throws Exception {
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
|
||||
.build());
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "1"));
|
||||
persistTestEntities("example.tld", false);
|
||||
setUpBillingEventForExistingDomain(NONPREMIUM, null);
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "11.00", "PERIOD", "1"),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_renewCommandPremium_internalRegistration() throws Exception {
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
|
||||
.build());
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "1"));
|
||||
persistTestEntities("example.tld", false);
|
||||
setUpBillingEventForExistingDomain(SPECIFIED, Money.of(USD, 3));
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "3.00", "PERIOD", "1"),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_renewCommandPremium_anchorTenant_multiYear() throws Exception {
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
|
||||
.build());
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "3"));
|
||||
persistTestEntities("example.tld", false);
|
||||
setUpBillingEventForExistingDomain(NONPREMIUM, null);
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "33.00", "PERIOD", "3"),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_renewCommandPremium_internalRegistration_multiYear() throws Exception {
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
|
||||
.build());
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "3"));
|
||||
persistTestEntities("example.tld", false);
|
||||
setUpBillingEventForExistingDomain(SPECIFIED, Money.of(USD, 3));
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "9.00", "PERIOD", "3"),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_renewCommandStandard_internalRegistration() throws Exception {
|
||||
createTld("tld");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "1"));
|
||||
persistTestEntities("example.tld", false);
|
||||
setUpBillingEventForExistingDomain(SPECIFIED, Money.of(USD, 3));
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "3.00", "PERIOD", "1"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test transfer command on a premium label. */
|
||||
@Test
|
||||
void testFeeExtension_transferCommandPremium() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "NAME", "rich.example", "COMMAND", "transfer", "PERIOD", "1"));
|
||||
persistTestEntities("rich.example", false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_premium_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "transfer", "DESCRIPTION", "renew"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test restore command on a premium label. */
|
||||
@Test
|
||||
void testFeeExtension_restoreCommandPremium() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "NAME", "rich.example", "COMMAND", "restore", "PERIOD", "1"));
|
||||
persistTestEntities("rich.example", false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_restore_premium_response.xml", false, ImmutableMap.of(), true);
|
||||
}
|
||||
|
||||
/** Test setting the currency explicitly to a wrong value. */
|
||||
@Test
|
||||
void testFeeExtension_wrongCurrency() {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "COMMAND", "create", "CURRENCY", "EUR", "PERIOD", "1"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/** Test requesting a period that isn't in years. */
|
||||
@Test
|
||||
void testFeeExtension_periodNotInYears() {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "create", "PERIOD", "2", "UNIT", "m"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(BadPeriodUnitException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/** Test a command that specifies a phase. */
|
||||
@Test
|
||||
void testFeeExtension_commandPhase() {
|
||||
setEppInput("domain_info_fee_command_phase.xml");
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(FeeChecksDontSupportPhasesException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/** Test a command that specifies a subphase. */
|
||||
@Test
|
||||
void testFeeExtension_commandSubphase() {
|
||||
setEppInput("domain_info_fee_command_subphase.xml");
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(FeeChecksDontSupportPhasesException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/** Test a restore for more than one year. */
|
||||
@Test
|
||||
void testFeeExtension_multiyearRestore() {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "restore", "PERIOD", "2"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(RestoresAreAlwaysForOneYearException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/** Test a transfer for more than one year. */
|
||||
@Test
|
||||
void testFeeExtension_multiyearTransfer() {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "transfer", "PERIOD", "2"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(TransfersAreAlwaysForOneYearException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_unknownCurrency() {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "COMMAND", "create", "CURRENCY", "BAD", "PERIOD", "1"));
|
||||
EppException thrown = assertThrows(UnknownCurrencyEppException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
}
|
||||
@@ -17,15 +17,12 @@ package google.registry.flows.domain;
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.DEFAULT;
|
||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.NONPREMIUM;
|
||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.SPECIFIED;
|
||||
import static google.registry.model.eppcommon.EppXmlTransformer.marshal;
|
||||
import static google.registry.model.tld.Tld.TldState.QUIET_PERIOD;
|
||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static google.registry.testing.DatabaseHelper.persistBillingRecurrenceForDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistPremiumList;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.testing.TestDataHelper.updateSubstitutions;
|
||||
@@ -41,15 +38,9 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.NotLoggedInException;
|
||||
import google.registry.flows.FlowUtils.UnknownCurrencyEppException;
|
||||
import google.registry.flows.ResourceFlowTestCase;
|
||||
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
|
||||
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.BadPeriodUnitException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.FeeChecksDontSupportPhasesException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.RestoresAreAlwaysForOneYearException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.TransfersAreAlwaysForOneYearException;
|
||||
import google.registry.model.billing.BillingBase.Flag;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
|
||||
@@ -586,353 +577,6 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create command. Fee extension version 6 is the only one which supports fee extensions on
|
||||
* info commands and responses, so we don't need to test the other versions.
|
||||
*/
|
||||
@Test
|
||||
void testFeeExtension_createCommand() throws Exception {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "create", "PERIOD", "2"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of(
|
||||
"COMMAND", "create",
|
||||
"DESCRIPTION", "create",
|
||||
"PERIOD", "2",
|
||||
"FEE", "24.00"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test renew command. */
|
||||
@Test
|
||||
void testFeeExtension_renewCommand() throws Exception {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "2"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of(
|
||||
"COMMAND", "renew",
|
||||
"DESCRIPTION", "renew",
|
||||
"PERIOD", "2",
|
||||
"FEE", "22.00"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test transfer command. */
|
||||
@Test
|
||||
void testFeeExtension_transferCommand() throws Exception {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "transfer", "PERIOD", "1"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of(
|
||||
"COMMAND", "transfer",
|
||||
"DESCRIPTION", "renew",
|
||||
"PERIOD", "1",
|
||||
"FEE", "11.00"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test restore command. */
|
||||
@Test
|
||||
void testFeeExtension_restoreCommand() throws Exception {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "restore", "PERIOD", "1"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest("domain_info_fee_restore_response.xml", false, ImmutableMap.of(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_restoreCommand_pendingDelete_noRenewal() throws Exception {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "restore", "PERIOD", "1"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setDeletionTime(clock.nowUtc().plusDays(25))
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.build());
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_restore_response_no_renewal.xml", false, ImmutableMap.of(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_restoreCommand_pendingDelete_withRenewal() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "NAME", "rich.example", "COMMAND", "restore", "PERIOD", "1"));
|
||||
persistTestEntities("rich.example", false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setDeletionTime(clock.nowUtc().plusDays(25))
|
||||
.setRegistrationExpirationTime(clock.nowUtc().minusDays(1))
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.build());
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_restore_response_with_renewal.xml", false, ImmutableMap.of(), true);
|
||||
}
|
||||
|
||||
/** Test create command on a premium label. */
|
||||
@Test
|
||||
void testFeeExtension_createCommandPremium() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "NAME", "rich.example", "COMMAND", "create", "PERIOD", "1"));
|
||||
persistTestEntities("rich.example", false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_premium_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "create", "DESCRIPTION", "create"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test renew command on a premium label. */
|
||||
@Test
|
||||
void testFeeExtension_renewCommandPremium() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "NAME", "rich.example", "COMMAND", "renew", "PERIOD", "1"));
|
||||
persistTestEntities("rich.example", false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_premium_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew"),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_renewCommandPremium_anchorTenant() throws Exception {
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
|
||||
.build());
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "1"));
|
||||
persistTestEntities("example.tld", false);
|
||||
setUpBillingEventForExistingDomain(NONPREMIUM, null);
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "11.00", "PERIOD", "1"),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_renewCommandPremium_internalRegistration() throws Exception {
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
|
||||
.build());
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "1"));
|
||||
persistTestEntities("example.tld", false);
|
||||
setUpBillingEventForExistingDomain(SPECIFIED, Money.of(USD, 3));
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "3.00", "PERIOD", "1"),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_renewCommandPremium_anchorTenant_multiYear() throws Exception {
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
|
||||
.build());
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "3"));
|
||||
persistTestEntities("example.tld", false);
|
||||
setUpBillingEventForExistingDomain(NONPREMIUM, null);
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "33.00", "PERIOD", "3"),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_renewCommandPremium_internalRegistration_multiYear() throws Exception {
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
|
||||
.build());
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "3"));
|
||||
persistTestEntities("example.tld", false);
|
||||
setUpBillingEventForExistingDomain(SPECIFIED, Money.of(USD, 3));
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "9.00", "PERIOD", "3"),
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_renewCommandStandard_internalRegistration() throws Exception {
|
||||
createTld("tld");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "1"));
|
||||
persistTestEntities("example.tld", false);
|
||||
setUpBillingEventForExistingDomain(SPECIFIED, Money.of(USD, 3));
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "3.00", "PERIOD", "1"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test transfer command on a premium label. */
|
||||
@Test
|
||||
void testFeeExtension_transferCommandPremium() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "NAME", "rich.example", "COMMAND", "transfer", "PERIOD", "1"));
|
||||
persistTestEntities("rich.example", false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_premium_response.xml",
|
||||
false,
|
||||
ImmutableMap.of("COMMAND", "transfer", "DESCRIPTION", "renew"),
|
||||
true);
|
||||
}
|
||||
|
||||
/** Test restore command on a premium label. */
|
||||
@Test
|
||||
void testFeeExtension_restoreCommandPremium() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "NAME", "rich.example", "COMMAND", "restore", "PERIOD", "1"));
|
||||
persistTestEntities("rich.example", false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_info_fee_restore_premium_response.xml", false, ImmutableMap.of(), true);
|
||||
}
|
||||
|
||||
/** Test setting the currency explicitly to a wrong value. */
|
||||
@Test
|
||||
void testFeeExtension_wrongCurrency() {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "COMMAND", "create", "CURRENCY", "EUR", "PERIOD", "1"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeeExtension_unknownCurrency() {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(
|
||||
SUBSTITUTION_BASE, "COMMAND", "create", "CURRENCY", "BAD", "PERIOD", "1"));
|
||||
EppException thrown = assertThrows(UnknownCurrencyEppException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/** Test requesting a period that isn't in years. */
|
||||
@Test
|
||||
void testFeeExtension_periodNotInYears() {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "create", "PERIOD", "2", "UNIT", "m"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(BadPeriodUnitException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/** Test a command that specifies a phase. */
|
||||
@Test
|
||||
void testFeeExtension_commandPhase() {
|
||||
setEppInput("domain_info_fee_command_phase.xml");
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(FeeChecksDontSupportPhasesException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/** Test a command that specifies a subphase. */
|
||||
@Test
|
||||
void testFeeExtension_commandSubphase() {
|
||||
setEppInput("domain_info_fee_command_subphase.xml");
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(FeeChecksDontSupportPhasesException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/** Test a restore for more than one year. */
|
||||
@Test
|
||||
void testFeeExtension_multiyearRestore() {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "restore", "PERIOD", "2"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(RestoresAreAlwaysForOneYearException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/** Test a transfer for more than one year. */
|
||||
@Test
|
||||
void testFeeExtension_multiyearTransfer() {
|
||||
setEppInput(
|
||||
"domain_info_fee.xml",
|
||||
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "transfer", "PERIOD", "2"));
|
||||
persistTestEntities(false);
|
||||
setUpBillingEventForExistingDomain();
|
||||
EppException thrown = assertThrows(TransfersAreAlwaysForOneYearException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIcannActivityReportField_getsLogged() throws Exception {
|
||||
persistTestEntities(false);
|
||||
|
||||
@@ -0,0 +1,827 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows.domain;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.DEFAULT;
|
||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.NONPREMIUM;
|
||||
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.SPECIFIED;
|
||||
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
|
||||
import static google.registry.testing.DatabaseHelper.assertBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.assertPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
|
||||
import static google.registry.testing.DatabaseHelper.loadByKey;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistPremiumList;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DatabaseHelper.persistResources;
|
||||
import static google.registry.testing.DomainSubject.assertAboutDomains;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.testing.HistoryEntrySubject.assertAboutHistoryEntries;
|
||||
import static google.registry.testing.TestDataHelper.updateSubstitutions;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.joda.money.CurrencyUnit.EUR;
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.CurrencyValueScaleException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.FeesMismatchException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.UnsupportedFeeAttributeException;
|
||||
import google.registry.model.billing.BillingBase.Flag;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Tests for {@link DomainRenewFlow} that use the old fee extensions (0.6, 0.11, 0.12). */
|
||||
public class DomainRenewFlowOldFeeExtensionsTest
|
||||
extends ProductionSimulatingFeeExtensionsTest<DomainRenewFlow> {
|
||||
|
||||
private static final ImmutableMap<String, String> FEE_BASE_MAP =
|
||||
ImmutableMap.of(
|
||||
"NAME", "example.tld",
|
||||
"PERIOD", "5",
|
||||
"EX_DATE", "2005-04-03T22:00:00.0Z",
|
||||
"FEE", "55.00",
|
||||
"CURRENCY", "USD");
|
||||
|
||||
private static final ImmutableMap<String, String> FEE_06_MAP =
|
||||
updateSubstitutions(FEE_BASE_MAP, "FEE_VERSION", "fee-0.6", "FEE_NS", "fee");
|
||||
private static final ImmutableMap<String, String> FEE_11_MAP =
|
||||
updateSubstitutions(FEE_BASE_MAP, "FEE_VERSION", "fee-0.11", "FEE_NS", "fee11");
|
||||
private static final ImmutableMap<String, String> FEE_12_MAP =
|
||||
updateSubstitutions(FEE_BASE_MAP, "FEE_VERSION", "fee-0.12", "FEE_NS", "fee12");
|
||||
|
||||
private final DateTime expirationTime = DateTime.parse("2000-04-03T22:00:00.0Z");
|
||||
|
||||
@BeforeEach
|
||||
void beforeEachDomainRenewFlowOldFeeExtensionsTest() {
|
||||
clock.setTo(expirationTime.minusMillis(20));
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
.setBillingAccountMap(ImmutableMap.of(USD, "123", EUR, "567"))
|
||||
.build());
|
||||
setEppInput("domain_renew.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "5"));
|
||||
}
|
||||
|
||||
private void persistDomain(StatusValue... statusValues) throws Exception {
|
||||
persistDomain(DEFAULT, null, statusValues);
|
||||
}
|
||||
|
||||
private void persistDomain(
|
||||
RenewalPriceBehavior renewalPriceBehavior,
|
||||
@Nullable Money renewalPrice,
|
||||
StatusValue... statusValues)
|
||||
throws Exception {
|
||||
Domain domain = DatabaseHelper.newDomain(getUniqueIdFromCommand());
|
||||
try {
|
||||
DomainHistory historyEntryDomainCreate =
|
||||
new DomainHistory.Builder()
|
||||
.setDomain(domain)
|
||||
.setType(HistoryEntry.Type.DOMAIN_CREATE)
|
||||
.setModificationTime(clock.nowUtc())
|
||||
.setRegistrarId(domain.getCreationRegistrarId())
|
||||
.build();
|
||||
BillingRecurrence autorenewEvent =
|
||||
new BillingRecurrence.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
.setTargetId(getUniqueIdFromCommand())
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(expirationTime)
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setDomainHistory(historyEntryDomainCreate)
|
||||
.setRenewalPriceBehavior(renewalPriceBehavior)
|
||||
.setRenewalPrice(renewalPrice)
|
||||
.build();
|
||||
PollMessage.Autorenew autorenewPollMessage =
|
||||
new PollMessage.Autorenew.Builder()
|
||||
.setTargetId(getUniqueIdFromCommand())
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(expirationTime)
|
||||
.setAutorenewEndTime(END_OF_TIME)
|
||||
.setMsg("Domain was auto-renewed.")
|
||||
.setHistoryEntry(historyEntryDomainCreate)
|
||||
.build();
|
||||
Domain newDomain =
|
||||
domain
|
||||
.asBuilder()
|
||||
.setRegistrationExpirationTime(expirationTime)
|
||||
.setStatusValues(ImmutableSet.copyOf(statusValues))
|
||||
.setAutorenewBillingEvent(autorenewEvent.createVKey())
|
||||
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
|
||||
.build();
|
||||
persistResources(
|
||||
ImmutableSet.of(
|
||||
historyEntryDomainCreate, autorenewEvent,
|
||||
autorenewPollMessage, newDomain));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error persisting domain", e);
|
||||
}
|
||||
clock.advanceOneMilli();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_11_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_12_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCurrency(EUR)
|
||||
.setCreateBillingCostTransitions(
|
||||
ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 13)))
|
||||
.setRestoreBillingCost(Money.of(EUR, 11))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 7)))
|
||||
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(EUR)))
|
||||
.setRegistryLockOrUnlockBillingCost(Money.of(EUR, 20))
|
||||
.setServerStatusChangeBillingCost(Money.of(EUR, 19))
|
||||
.build());
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_11_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCurrency(EUR)
|
||||
.setCreateBillingCostTransitions(
|
||||
ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 13)))
|
||||
.setRestoreBillingCost(Money.of(EUR, 11))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 7)))
|
||||
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(EUR)))
|
||||
.setRegistryLockOrUnlockBillingCost(Money.of(EUR, 20))
|
||||
.setServerStatusChangeBillingCost(Money.of(EUR, 19))
|
||||
.build());
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_12_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCurrency(EUR)
|
||||
.setCreateBillingCostTransitions(
|
||||
ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 13)))
|
||||
.setRestoreBillingCost(Money.of(EUR, 11))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 7)))
|
||||
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(EUR)))
|
||||
.setRegistryLockOrUnlockBillingCost(Money.of(EUR, 20))
|
||||
.setServerStatusChangeBillingCost(Money.of(EUR, 19))
|
||||
.build());
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee_bad_scale.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee_bad_scale.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee_bad_scale.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_doesNotApplyNonPremiumDefaultTokenToPremiumName_v06() throws Exception {
|
||||
ImmutableMap<String, String> customFeeMap = updateSubstitutions(FEE_06_MAP, "FEE", "500");
|
||||
setEppInput("domain_renew_fee.xml", customFeeMap);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("aaaaa")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(1)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
|
||||
.setPremiumList(persistPremiumList("tld", USD, "example,USD 100"))
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_renew_response_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"NAME",
|
||||
"example.tld",
|
||||
"PERIOD",
|
||||
"5",
|
||||
"EX_DATE",
|
||||
"2005-04-03T22:00:00.0Z",
|
||||
"FEE",
|
||||
"500.00",
|
||||
"CURRENCY",
|
||||
"USD",
|
||||
"FEE_VERSION",
|
||||
"fee-0.6",
|
||||
"FEE_NS",
|
||||
"fee")));
|
||||
BillingEvent billingEvent =
|
||||
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.class));
|
||||
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
|
||||
assertThat(billingEvent.getAllocationToken()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_internalRegiration_premiumDomain_v06() throws Exception {
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setPremiumList(persistPremiumList("tld", USD, "example,USD 100"))
|
||||
.build());
|
||||
persistDomain(SPECIFIED, Money.of(USD, 2));
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
ImmutableMap<String, String> customFeeMap = updateSubstitutions(FEE_06_MAP, "FEE", "10.00");
|
||||
setEppInput("domain_renew_fee.xml", customFeeMap);
|
||||
doSuccessfulTest(
|
||||
"domain_renew_response_fee.xml",
|
||||
5,
|
||||
"NewRegistrar",
|
||||
UserPrivileges.SUPERUSER,
|
||||
customFeeMap,
|
||||
Money.of(USD, 10),
|
||||
SPECIFIED,
|
||||
Money.of(USD, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("aaaaa")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(1)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_renew_response_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"NAME",
|
||||
"example.tld",
|
||||
"PERIOD",
|
||||
"5",
|
||||
"EX_DATE",
|
||||
"2005-04-03T22:00:00.0Z",
|
||||
"FEE",
|
||||
"49.50",
|
||||
"CURRENCY",
|
||||
"USD",
|
||||
"FEE_VERSION",
|
||||
"fee-0.6",
|
||||
"FEE_NS",
|
||||
"fee")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("aaaaa")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(1)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("aaaaa")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(1)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_renew_response_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"NAME",
|
||||
"example.tld",
|
||||
"PERIOD",
|
||||
"5",
|
||||
"EX_DATE",
|
||||
"2005-04-03T22:00:00.0Z",
|
||||
"FEE",
|
||||
"49.50",
|
||||
"CURRENCY",
|
||||
"USD",
|
||||
"FEE_VERSION",
|
||||
"fee-0.11",
|
||||
"FEE_NS",
|
||||
"fee")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("aaaaa")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(1)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("aaaaa")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(1)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_renew_response_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"NAME",
|
||||
"example.tld",
|
||||
"PERIOD",
|
||||
"5",
|
||||
"EX_DATE",
|
||||
"2005-04-03T22:00:00.0Z",
|
||||
"FEE",
|
||||
"49.50",
|
||||
"CURRENCY",
|
||||
"USD",
|
||||
"FEE_VERSION",
|
||||
"fee-0.12",
|
||||
"FEE_NS",
|
||||
"fee")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("aaaaa")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(1)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee_refundable.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee_refundable.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee_refundable.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee_grace_period.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee_grace_period.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee_grace_period.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee_applied.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee_applied.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee_applied.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_12_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee_defaults.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee_defaults.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee_defaults.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_12_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_anchorTenant_premiumDomain_v06() throws Exception {
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setPremiumList(persistPremiumList("tld", USD, "example,USD 100"))
|
||||
.build());
|
||||
persistDomain(NONPREMIUM, null);
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
ImmutableMap<String, String> customFeeMap = updateSubstitutions(FEE_06_MAP, "FEE", "55.00");
|
||||
setEppInput("domain_renew_fee.xml", customFeeMap);
|
||||
doSuccessfulTest(
|
||||
"domain_renew_response_fee.xml",
|
||||
5,
|
||||
"NewRegistrar",
|
||||
UserPrivileges.SUPERUSER,
|
||||
customFeeMap,
|
||||
Money.of(USD, 55),
|
||||
NONPREMIUM,
|
||||
null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_customLogicFee_v06() throws Exception {
|
||||
// The "costly-renew" domain has an additional RENEW fee of 100 from custom logic on top of the
|
||||
// normal $11 standard renew price for this TLD.
|
||||
ImmutableMap<String, String> customFeeMap =
|
||||
updateSubstitutions(
|
||||
FEE_06_MAP,
|
||||
"NAME",
|
||||
"costly-renew.tld",
|
||||
"PERIOD",
|
||||
"1",
|
||||
"EX_DATE",
|
||||
"2001-04-03T22:00:00.0Z",
|
||||
"FEE",
|
||||
"111.00");
|
||||
setEppInput("domain_renew_fee.xml", customFeeMap);
|
||||
persistDomain();
|
||||
doSuccessfulTest(
|
||||
"domain_renew_response_fee.xml",
|
||||
1,
|
||||
"TheRegistrar",
|
||||
UserPrivileges.NORMAL,
|
||||
customFeeMap,
|
||||
Money.of(USD, 111));
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(String responseFilename, int renewalYears) throws Exception {
|
||||
doSuccessfulTest(responseFilename, renewalYears, ImmutableMap.of());
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(
|
||||
String responseFilename, int renewalYears, Map<String, String> substitutions)
|
||||
throws Exception {
|
||||
doSuccessfulTest(
|
||||
responseFilename,
|
||||
renewalYears,
|
||||
"TheRegistrar",
|
||||
UserPrivileges.NORMAL,
|
||||
substitutions,
|
||||
Money.of(USD, 11).multipliedBy(renewalYears),
|
||||
DEFAULT,
|
||||
null);
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(
|
||||
String responseFilename,
|
||||
int renewalYears,
|
||||
String renewalClientId,
|
||||
UserPrivileges userPrivileges,
|
||||
Map<String, String> substitutions,
|
||||
Money totalRenewCost)
|
||||
throws Exception {
|
||||
doSuccessfulTest(
|
||||
responseFilename,
|
||||
renewalYears,
|
||||
renewalClientId,
|
||||
userPrivileges,
|
||||
substitutions,
|
||||
totalRenewCost,
|
||||
DEFAULT,
|
||||
null);
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(
|
||||
String responseFilename,
|
||||
int renewalYears,
|
||||
String renewalClientId,
|
||||
UserPrivileges userPrivileges,
|
||||
Map<String, String> substitutions,
|
||||
Money totalRenewCost,
|
||||
RenewalPriceBehavior renewalPriceBehavior,
|
||||
@Nullable Money renewalPrice)
|
||||
throws Exception {
|
||||
assertMutatingFlow(true);
|
||||
DateTime currentExpiration = reloadResourceByForeignKey().getRegistrationExpirationTime();
|
||||
DateTime newExpiration = currentExpiration.plusYears(renewalYears);
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE, userPrivileges, loadFile(responseFilename, substitutions));
|
||||
Domain domain = reloadResourceByForeignKey();
|
||||
assertLastHistoryContainsResource(domain);
|
||||
DomainHistory historyEntryDomainRenew =
|
||||
getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_RENEW, DomainHistory.class);
|
||||
assertThat(loadByKey(domain.getAutorenewBillingEvent()).getEventTime())
|
||||
.isEqualTo(newExpiration);
|
||||
assertAboutDomains()
|
||||
.that(domain)
|
||||
.isActiveAt(clock.nowUtc())
|
||||
.and()
|
||||
.hasRegistrationExpirationTime(newExpiration)
|
||||
.and()
|
||||
.hasOneHistoryEntryEachOfTypes(
|
||||
HistoryEntry.Type.DOMAIN_CREATE, HistoryEntry.Type.DOMAIN_RENEW)
|
||||
.and()
|
||||
.hasLastEppUpdateTime(clock.nowUtc())
|
||||
.and()
|
||||
.hasLastEppUpdateRegistrarId(renewalClientId);
|
||||
assertAboutHistoryEntries().that(historyEntryDomainRenew).hasPeriodYears(renewalYears);
|
||||
BillingEvent renewBillingEvent =
|
||||
new BillingEvent.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setTargetId(getUniqueIdFromCommand())
|
||||
.setRegistrarId(renewalClientId)
|
||||
.setCost(totalRenewCost)
|
||||
.setPeriodYears(renewalYears)
|
||||
.setEventTime(clock.nowUtc())
|
||||
.setBillingTime(clock.nowUtc().plus(Tld.get("tld").getRenewGracePeriodLength()))
|
||||
.setDomainHistory(historyEntryDomainRenew)
|
||||
.build();
|
||||
assertBillingEvents(
|
||||
renewBillingEvent,
|
||||
new BillingRecurrence.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setRenewalPriceBehavior(renewalPriceBehavior)
|
||||
.setRenewalPrice(renewalPrice)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
.setTargetId(getUniqueIdFromCommand())
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(expirationTime)
|
||||
.setRecurrenceEndTime(clock.nowUtc())
|
||||
.setDomainHistory(
|
||||
getOnlyHistoryEntryOfType(
|
||||
domain, HistoryEntry.Type.DOMAIN_CREATE, DomainHistory.class))
|
||||
.build(),
|
||||
new BillingRecurrence.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setRenewalPriceBehavior(renewalPriceBehavior)
|
||||
.setRenewalPrice(renewalPrice)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
.setTargetId(getUniqueIdFromCommand())
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(domain.getRegistrationExpirationTime())
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setDomainHistory(historyEntryDomainRenew)
|
||||
.build());
|
||||
// There should only be the new autorenew poll message, as the old one will have been deleted
|
||||
// since it had no messages left to deliver.
|
||||
assertPollMessages(
|
||||
new PollMessage.Autorenew.Builder()
|
||||
.setTargetId(getUniqueIdFromCommand())
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(domain.getRegistrationExpirationTime())
|
||||
.setAutorenewEndTime(END_OF_TIME)
|
||||
.setMsg("Domain was auto-renewed.")
|
||||
.setHistoryEntry(historyEntryDomainRenew)
|
||||
.build());
|
||||
assertGracePeriods(
|
||||
domain.getGracePeriods(),
|
||||
ImmutableMap.of(
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.RENEW,
|
||||
domain.getRepoId(),
|
||||
clock.nowUtc().plus(Tld.get("tld").getRenewGracePeriodLength()),
|
||||
renewalClientId,
|
||||
null),
|
||||
renewBillingEvent));
|
||||
}
|
||||
}
|
||||
@@ -119,12 +119,8 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
"FEE", "55.00",
|
||||
"CURRENCY", "USD");
|
||||
|
||||
private static final ImmutableMap<String, String> FEE_06_MAP =
|
||||
updateSubstitutions(FEE_BASE_MAP, "FEE_VERSION", "0.6", "FEE_NS", "fee");
|
||||
private static final ImmutableMap<String, String> FEE_11_MAP =
|
||||
updateSubstitutions(FEE_BASE_MAP, "FEE_VERSION", "0.11", "FEE_NS", "fee11");
|
||||
private static final ImmutableMap<String, String> FEE_12_MAP =
|
||||
updateSubstitutions(FEE_BASE_MAP, "FEE_VERSION", "0.12", "FEE_NS", "fee12");
|
||||
private static final ImmutableMap<String, String> FEE_STD_1_0_MAP =
|
||||
updateSubstitutions(FEE_BASE_MAP, "FEE_VERSION", "epp:fee-1.0", "FEE_NS", "fee1_00");
|
||||
|
||||
private final DateTime expirationTime = DateTime.parse("2000-04-03T22:00:00.0Z");
|
||||
|
||||
@@ -390,7 +386,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_internalRegiration_premiumDomain() throws Exception {
|
||||
void testSuccess_internalRegiration_premiumDomain_std_v1() throws Exception {
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
@@ -398,7 +394,8 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
.build());
|
||||
persistDomain(SPECIFIED, Money.of(USD, 2));
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
ImmutableMap<String, String> customFeeMap = updateSubstitutions(FEE_06_MAP, "FEE", "10.00");
|
||||
ImmutableMap<String, String> customFeeMap =
|
||||
updateSubstitutions(FEE_STD_1_0_MAP, "FEE", "10.00");
|
||||
setEppInput("domain_renew_fee.xml", customFeeMap);
|
||||
doSuccessfulTest(
|
||||
"domain_renew_response_fee.xml",
|
||||
@@ -427,7 +424,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_anchorTenant_premiumDomain() throws Exception {
|
||||
void testSuccess_anchorTenant_premiumDomain_std_v1() throws Exception {
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
@@ -435,7 +432,8 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
.build());
|
||||
persistDomain(NONPREMIUM, null);
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
ImmutableMap<String, String> customFeeMap = updateSubstitutions(FEE_06_MAP, "FEE", "55.00");
|
||||
ImmutableMap<String, String> customFeeMap =
|
||||
updateSubstitutions(FEE_STD_1_0_MAP, "FEE", "55.00");
|
||||
setEppInput("domain_renew_fee.xml", customFeeMap);
|
||||
doSuccessfulTest(
|
||||
"domain_renew_response_fee.xml",
|
||||
@@ -449,12 +447,12 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_customLogicFee() throws Exception {
|
||||
void testSuccess_customLogicFee_std_v1() throws Exception {
|
||||
// The "costly-renew" domain has an additional RENEW fee of 100 from custom logic on top of the
|
||||
// normal $11 standard renew price for this TLD.
|
||||
ImmutableMap<String, String> customFeeMap =
|
||||
updateSubstitutions(
|
||||
FEE_06_MAP,
|
||||
FEE_STD_1_0_MAP,
|
||||
"NAME",
|
||||
"costly-renew.tld",
|
||||
"PERIOD",
|
||||
@@ -475,121 +473,45 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
void testSuccess_fee_std_v1() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_06_MAP);
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_STD_1_0_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_11_MAP);
|
||||
void testSuccess_fee_withDefaultAttributes_std_v1() throws Exception {
|
||||
setEppInput("domain_renew_fee_defaults.xml", FEE_STD_1_0_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_12_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee_defaults.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee_defaults.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee_defaults.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_12_MAP);
|
||||
doSuccessfulTest("domain_renew_response_fee.xml", 5, FEE_STD_1_0_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_fee_unknownCurrency() {
|
||||
setEppInput("domain_renew_fee.xml", updateSubstitutions(FEE_06_MAP, "CURRENCY", "BAD"));
|
||||
setEppInput("domain_renew_fee.xml", updateSubstitutions(FEE_STD_1_0_MAP, "CURRENCY", "BAD"));
|
||||
EppException thrown = assertThrows(UnknownCurrencyEppException.class, this::persistDomain);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee_refundable.xml", FEE_06_MAP);
|
||||
void testFailure_refundableFee_std_v1() throws Exception {
|
||||
setEppInput("domain_renew_fee_refundable.xml", FEE_STD_1_0_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee_refundable.xml", FEE_11_MAP);
|
||||
void testFailure_gracePeriodFee_std_v1() throws Exception {
|
||||
setEppInput("domain_renew_fee_grace_period.xml", FEE_STD_1_0_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee_refundable.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee_grace_period.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee_grace_period.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee_grace_period.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee_applied.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee_applied.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee_applied.xml", FEE_12_MAP);
|
||||
void testFailure_appliedFee_std_v1() throws Exception {
|
||||
setEppInput("domain_renew_fee_applied.xml", FEE_STD_1_0_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -991,8 +913,8 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
void testFailure_wrongFeeAmount_std_v1() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
@@ -1004,34 +926,8 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_11_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_12_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
void testFailure_wrongCurrency_std_v1() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
@@ -1050,64 +946,8 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_11_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCurrency(EUR)
|
||||
.setCreateBillingCostTransitions(
|
||||
ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 13)))
|
||||
.setRestoreBillingCost(Money.of(EUR, 11))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 7)))
|
||||
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(EUR)))
|
||||
.setRegistryLockOrUnlockBillingCost(Money.of(EUR, 20))
|
||||
.setServerStatusChangeBillingCost(Money.of(EUR, 19))
|
||||
.build());
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_12_MAP);
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCurrency(EUR)
|
||||
.setCreateBillingCostTransitions(
|
||||
ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 13)))
|
||||
.setRestoreBillingCost(Money.of(EUR, 11))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 7)))
|
||||
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(EUR)))
|
||||
.setRegistryLockOrUnlockBillingCost(Money.of(EUR, 20))
|
||||
.setServerStatusChangeBillingCost(Money.of(EUR, 19))
|
||||
.build());
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee_bad_scale.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee_bad_scale.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee_bad_scale.xml", FEE_12_MAP);
|
||||
void testFailure_feeGivenInWrongScale_std_v1() throws Exception {
|
||||
setEppInput("domain_renew_fee_bad_scale.xml", FEE_STD_1_0_MAP);
|
||||
persistDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -1522,8 +1362,8 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_doesNotApplyNonPremiumDefaultTokenToPremiumName() throws Exception {
|
||||
ImmutableMap<String, String> customFeeMap = updateSubstitutions(FEE_06_MAP, "FEE", "500");
|
||||
void testSuccess_doesNotApplyNonPremiumDefaultTokenToPremiumName_std_v1() throws Exception {
|
||||
ImmutableMap<String, String> customFeeMap = updateSubstitutions(FEE_STD_1_0_MAP, "FEE", "500");
|
||||
setEppInput("domain_renew_fee.xml", customFeeMap);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
@@ -1556,9 +1396,9 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
"CURRENCY",
|
||||
"USD",
|
||||
"FEE_VERSION",
|
||||
"0.6",
|
||||
"epp:fee-1.0",
|
||||
"FEE_NS",
|
||||
"fee")));
|
||||
"fee1_00")));
|
||||
BillingEvent billingEvent =
|
||||
Iterables.getOnlyElement(DatabaseHelper.loadAllOf(BillingEvent.class));
|
||||
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
|
||||
@@ -1662,8 +1502,8 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_std_v1() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
@@ -1694,136 +1534,14 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
|
||||
"CURRENCY",
|
||||
"USD",
|
||||
"FEE_VERSION",
|
||||
"0.6",
|
||||
"epp:fee-1.0",
|
||||
"FEE_NS",
|
||||
"fee")));
|
||||
"fee1_00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v06() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("aaaaa")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(1)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("aaaaa")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(1)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_renew_response_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"NAME",
|
||||
"example.tld",
|
||||
"PERIOD",
|
||||
"5",
|
||||
"EX_DATE",
|
||||
"2005-04-03T22:00:00.0Z",
|
||||
"FEE",
|
||||
"49.50",
|
||||
"CURRENCY",
|
||||
"USD",
|
||||
"FEE_VERSION",
|
||||
"0.11",
|
||||
"FEE_NS",
|
||||
"fee")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v11() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_11_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("aaaaa")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(1)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_wrongFeeAmountTooHigh_defaultToken_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_12_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("aaaaa")
|
||||
.setTokenType(DEFAULT_PROMO)
|
||||
.setDiscountFraction(0.5)
|
||||
.setDiscountYears(1)
|
||||
.setAllowedTlds(ImmutableSet.of("tld"))
|
||||
.build());
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setDefaultPromoTokens(ImmutableList.of(defaultToken1.createVKey()))
|
||||
.build());
|
||||
runFlowAssertResponse(
|
||||
loadFile(
|
||||
"domain_renew_response_fee.xml",
|
||||
ImmutableMap.of(
|
||||
"NAME",
|
||||
"example.tld",
|
||||
"PERIOD",
|
||||
"5",
|
||||
"EX_DATE",
|
||||
"2005-04-03T22:00:00.0Z",
|
||||
"FEE",
|
||||
"49.50",
|
||||
"CURRENCY",
|
||||
"USD",
|
||||
"FEE_VERSION",
|
||||
"0.12",
|
||||
"FEE_NS",
|
||||
"fee")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_v12() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_06_MAP);
|
||||
void testFailure_wrongFeeAmountTooLow_defaultToken_std_v1() throws Exception {
|
||||
setEppInput("domain_renew_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistDomain();
|
||||
AllocationToken defaultToken1 =
|
||||
persistResource(
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows.domain;
|
||||
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.joda.money.CurrencyUnit.EUR;
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.FlowUtils.UnknownCurrencyEppException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.CurrencyValueScaleException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.FeesMismatchException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.PremiumNameBlockedException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.UnsupportedFeeAttributeException;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import java.util.Map;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Tests for {@link DomainRestoreRequestFlow} that use the old fee extensions (0.6, 0.11, 0.12). */
|
||||
public class DomainRestoreRequestFlowOldFeeExtensionsTest
|
||||
extends ProductionSimulatingFeeExtensionsTest<DomainRestoreRequestFlow> {
|
||||
|
||||
private static final ImmutableMap<String, String> FEE_06_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "fee-0.6", "FEE_NS", "fee", "CURRENCY", "USD");
|
||||
private static final ImmutableMap<String, String> FEE_11_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "fee-0.11", "FEE_NS", "fee11", "CURRENCY", "USD");
|
||||
private static final ImmutableMap<String, String> FEE_12_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "fee-0.12", "FEE_NS", "fee12", "CURRENCY", "USD");
|
||||
|
||||
@BeforeEach
|
||||
void beforeEachDomainRestoreRequestFlowOldFeeExtensionsTest() {
|
||||
createTld("tld");
|
||||
persistResource(
|
||||
loadRegistrar("TheRegistrar")
|
||||
.asBuilder()
|
||||
.setBillingAccountMap(ImmutableMap.of(USD, "123", EUR, "567"))
|
||||
.build());
|
||||
setEppInput("domain_update_restore_request.xml", ImmutableMap.of("DOMAIN", "example.tld"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
persistResource(Tld.get("tld").asBuilder().setRestoreBillingCost(Money.of(USD, 100)).build());
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
persistResource(Tld.get("tld").asBuilder().setRestoreBillingCost(Money.of(USD, 100)).build());
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
persistResource(Tld.get("tld").asBuilder().setRestoreBillingCost(Money.of(USD, 100)).build());
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_bad_scale.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_bad_scale.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_bad_scale.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_applied.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_applied.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_applied.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_defaults.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_06_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_defaults.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_11_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_defaults.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_12_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_06_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v06_noRenewal() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_no_renewal.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain(clock.nowUtc().plusMonths(6));
|
||||
runFlowAssertResponse(
|
||||
loadFile("domain_update_restore_request_response_fee_no_renewal.xml", FEE_06_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_11_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_12_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_refundable.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_refundable.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_refundable.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_grace_period.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_grace_period.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_grace_period.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
private void runWrongCurrencyTest(Map<String, String> substitutions) throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", substitutions);
|
||||
persistPendingDeleteDomain();
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setCurrency(EUR)
|
||||
.setCreateBillingCostTransitions(
|
||||
ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 13)))
|
||||
.setRestoreBillingCost(Money.of(EUR, 11))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 7)))
|
||||
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(EUR)))
|
||||
.setServerStatusChangeBillingCost(Money.of(EUR, 19))
|
||||
.setRegistryLockOrUnlockBillingCost(Money.of(EUR, 0))
|
||||
.build());
|
||||
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v06() throws Exception {
|
||||
runWrongCurrencyTest(FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v11() throws Exception {
|
||||
runWrongCurrencyTest(FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v12() throws Exception {
|
||||
runWrongCurrencyTest(FEE_12_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_premiumBlocked_v12() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput("domain_update_restore_request_premium.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
// Modify the Registrar to block premium names.
|
||||
persistResource(loadRegistrar("TheRegistrar").asBuilder().setBlockPremiumNames(true).build());
|
||||
EppException thrown = assertThrows(PremiumNameBlockedException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_premiumNotBlocked_andNoRenewal_v12() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput("domain_update_restore_request_premium_no_renewal.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain(clock.nowUtc().plusYears(2));
|
||||
runFlowAssertResponse(
|
||||
loadFile("domain_update_restore_request_response_fee_no_renewal.xml", FEE_12_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_fee_unknownCurrency_v12() {
|
||||
ImmutableMap<String, String> substitutions =
|
||||
ImmutableMap.of("FEE_VERSION", "fee-0.12", "FEE_NS", "fee12", "CURRENCY", "BAD");
|
||||
setEppInput("domain_update_restore_request_fee.xml", substitutions);
|
||||
EppException thrown =
|
||||
assertThrows(UnknownCurrencyEppException.class, this::persistPendingDeleteDomain);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
private Domain persistPendingDeleteDomain() throws Exception {
|
||||
// The domain is now past what had been its expiration date at the time of deletion.
|
||||
return persistPendingDeleteDomain(clock.nowUtc().minusDays(5));
|
||||
}
|
||||
|
||||
private Domain persistPendingDeleteDomain(DateTime expirationTime) throws Exception {
|
||||
Domain domain = persistResource(DatabaseHelper.newDomain(getUniqueIdFromCommand()));
|
||||
HistoryEntry historyEntry =
|
||||
persistResource(
|
||||
new DomainHistory.Builder()
|
||||
.setType(HistoryEntry.Type.DOMAIN_DELETE)
|
||||
.setModificationTime(clock.nowUtc())
|
||||
.setRegistrarId(domain.getCurrentSponsorRegistrarId())
|
||||
.setDomain(domain)
|
||||
.build());
|
||||
domain =
|
||||
persistResource(
|
||||
domain
|
||||
.asBuilder()
|
||||
.setRegistrationExpirationTime(expirationTime)
|
||||
.setDeletionTime(clock.nowUtc().plusDays(35))
|
||||
.addGracePeriod(
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.REDEMPTION,
|
||||
domain.getRepoId(),
|
||||
clock.nowUtc().plusDays(1),
|
||||
"TheRegistrar",
|
||||
null))
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.setDeletePollMessage(
|
||||
persistResource(
|
||||
new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(clock.nowUtc().plusDays(5))
|
||||
.setHistoryEntry(historyEntry)
|
||||
.build())
|
||||
.createVKey())
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
@@ -86,12 +86,8 @@ import org.junit.jupiter.api.Test;
|
||||
/** Unit tests for {@link DomainRestoreRequestFlow}. */
|
||||
class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreRequestFlow, Domain> {
|
||||
|
||||
private static final ImmutableMap<String, String> FEE_06_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "0.6", "FEE_NS", "fee", "CURRENCY", "USD");
|
||||
private static final ImmutableMap<String, String> FEE_11_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "0.11", "FEE_NS", "fee11", "CURRENCY", "USD");
|
||||
private static final ImmutableMap<String, String> FEE_12_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE_NS", "fee12", "CURRENCY", "USD");
|
||||
private static final ImmutableMap<String, String> FEE_STD_1_0_MAP =
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "FEE_NS", "fee1_00", "CURRENCY", "USD");
|
||||
|
||||
@BeforeEach
|
||||
void initDomainTest() {
|
||||
@@ -308,7 +304,7 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
|
||||
|
||||
@Test
|
||||
void testSuccess_autorenewEndTimeIsCleared() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_06_MAP);
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
persistResource(
|
||||
reloadResourceByForeignKey()
|
||||
@@ -316,64 +312,31 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
|
||||
.setAutorenewEndTime(Optional.of(clock.nowUtc().plusYears(2)))
|
||||
.build());
|
||||
assertThat(reloadResourceByForeignKey().getAutorenewEndTime()).isPresent();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_06_MAP));
|
||||
runFlowAssertResponse(
|
||||
loadFile("domain_update_restore_request_response_fee.xml", FEE_STD_1_0_MAP));
|
||||
assertThat(reloadResourceByForeignKey().getAutorenewEndTime()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_06_MAP);
|
||||
void testSuccess_fee_std_v1() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_06_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v06_noRenewal() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_no_renewal.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain(clock.nowUtc().plusMonths(6));
|
||||
runFlowAssertResponse(
|
||||
loadFile("domain_update_restore_request_response_fee_no_renewal.xml", FEE_06_MAP));
|
||||
loadFile("domain_update_restore_request_response_fee.xml", FEE_STD_1_0_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_11_MAP);
|
||||
void testSuccess_fee_withDefaultAttributes_std_v1() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_defaults.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_11_MAP));
|
||||
runFlowAssertResponse(
|
||||
loadFile("domain_update_restore_request_response_fee.xml", FEE_STD_1_0_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_12_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_defaults.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_06_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_defaults.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_11_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_defaults.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_fee.xml", FEE_12_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_fee_unknownCurrency() {
|
||||
void testFailure_fee_unknownCurrency_std_v1() {
|
||||
ImmutableMap<String, String> substitutions =
|
||||
ImmutableMap.of("FEE_VERSION", "0.12", "FEE_NS", "fee12", "CURRENCY", "BAD");
|
||||
ImmutableMap.of("FEE_VERSION", "epp:fee-1.0", "FEE_NS", "fee1_00", "CURRENCY", "BAD");
|
||||
setEppInput("domain_update_restore_request_fee.xml", substitutions);
|
||||
EppException thrown =
|
||||
assertThrows(UnknownCurrencyEppException.class, this::persistPendingDeleteDomain);
|
||||
@@ -381,72 +344,24 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_refundable.xml", FEE_06_MAP);
|
||||
void testFailure_refundableFee_std_v1() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_refundable.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_refundable.xml", FEE_11_MAP);
|
||||
void testFailure_gracePeriodFee_std_v1() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_grace_period.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_refundable.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_grace_period.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_grace_period.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_grace_period.xml", FEE_12_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_applied.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_applied.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_applied.xml", FEE_12_MAP);
|
||||
void testFailure_appliedFee_std_v1() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_applied.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(UnsupportedFeeAttributeException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -455,18 +370,19 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
|
||||
@Test
|
||||
void testSuccess_premiumNotBlocked() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput("domain_update_restore_request_premium.xml");
|
||||
setEppInput("domain_update_restore_request_premium.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
runFlowAssertResponse(loadFile("domain_update_restore_request_response_premium.xml"));
|
||||
runFlowAssertResponse(
|
||||
loadFile("domain_update_restore_request_response_premium.xml", FEE_STD_1_0_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_premiumNotBlocked_andNoRenewal() throws Exception {
|
||||
void testSuccess_premiumNotBlocked_andNoRenewal_std_v1() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput("domain_update_restore_request_premium_no_renewal.xml");
|
||||
setEppInput("domain_update_restore_request_premium_no_renewal.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain(clock.nowUtc().plusYears(2));
|
||||
runFlowAssertResponse(
|
||||
loadFile("domain_update_restore_request_response_fee_no_renewal.xml", FEE_12_MAP));
|
||||
loadFile("domain_update_restore_request_response_fee_no_renewal.xml", FEE_STD_1_0_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -484,14 +400,14 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
|
||||
@Test
|
||||
void testSuccess_superuserOverridesPremiumNameBlock() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput("domain_update_restore_request_premium.xml");
|
||||
setEppInput("domain_update_restore_request_premium.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
// Modify the Registrar to block premium names.
|
||||
persistResource(loadRegistrar("TheRegistrar").asBuilder().setBlockPremiumNames(true).build());
|
||||
runFlowAssertResponse(
|
||||
CommitMode.LIVE,
|
||||
UserPrivileges.SUPERUSER,
|
||||
loadFile("domain_update_restore_request_response_premium.xml"));
|
||||
loadFile("domain_update_restore_request_response_premium.xml", FEE_STD_1_0_MAP));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -538,26 +454,8 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
persistResource(Tld.get("tld").asBuilder().setRestoreBillingCost(Money.of(USD, 100)).build());
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
persistResource(Tld.get("tld").asBuilder().setRestoreBillingCost(Money.of(USD, 100)).build());
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_12_MAP);
|
||||
void testFailure_wrongFeeAmount_std_v1() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
persistResource(Tld.get("tld").asBuilder().setRestoreBillingCost(Money.of(USD, 100)).build());
|
||||
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
|
||||
@@ -584,39 +482,13 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v06() throws Exception {
|
||||
runWrongCurrencyTest(FEE_06_MAP);
|
||||
void testFailure_wrongCurrency_std_v1() throws Exception {
|
||||
runWrongCurrencyTest(FEE_STD_1_0_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v11() throws Exception {
|
||||
runWrongCurrencyTest(FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v12() throws Exception {
|
||||
runWrongCurrencyTest(FEE_12_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v06() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_bad_scale.xml", FEE_06_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v11() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_bad_scale.xml", FEE_11_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v12() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_bad_scale.xml", FEE_12_MAP);
|
||||
void testFailure_feeGivenInWrongScale_std_v1() throws Exception {
|
||||
setEppInput("domain_update_restore_request_fee_bad_scale.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
EppException thrown = assertThrows(CurrencyValueScaleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -738,9 +610,9 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_premiumBlocked() throws Exception {
|
||||
void testFailure_premiumBlocked_std_v1() throws Exception {
|
||||
createTld("example");
|
||||
setEppInput("domain_update_restore_request_premium.xml");
|
||||
setEppInput("domain_update_restore_request_premium.xml", FEE_STD_1_0_MAP);
|
||||
persistPendingDeleteDomain();
|
||||
// Modify the Registrar to block premium names.
|
||||
persistResource(loadRegistrar("TheRegistrar").asBuilder().setBlockPremiumNames(true).build());
|
||||
|
||||
@@ -0,0 +1,897 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.flows.domain;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_REQUESTED_TIME;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST;
|
||||
import static google.registry.testing.DatabaseHelper.assertBillingEvents;
|
||||
import static google.registry.testing.DatabaseHelper.assertBillingEventsEqual;
|
||||
import static google.registry.testing.DatabaseHelper.assertPollMessagesEqual;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
|
||||
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
|
||||
import static google.registry.testing.DatabaseHelper.getPollMessages;
|
||||
import static google.registry.testing.DatabaseHelper.loadByKey;
|
||||
import static google.registry.testing.DatabaseHelper.loadByKeys;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||
import static google.registry.testing.DatabaseHelper.persistDomainWithDependentResources;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.testing.DomainSubject.assertAboutDomains;
|
||||
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
|
||||
import static google.registry.testing.HistoryEntrySubject.assertAboutHistoryEntries;
|
||||
import static google.registry.testing.HostSubject.assertAboutHosts;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.google.cloud.tasks.v2.HttpMethod;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.batch.ResaveEntityAction;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.domain.DomainFlowUtils.CurrencyUnitMismatchException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.CurrencyValueScaleException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.FeesMismatchException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.UnsupportedFeeAttributeException;
|
||||
import google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException;
|
||||
import google.registry.model.billing.BillingBase;
|
||||
import google.registry.model.billing.BillingBase.Flag;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingCancellation;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingRecurrence;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.Period.Unit;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.transfer.DomainTransferData;
|
||||
import google.registry.model.transfer.TransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.testing.CloudTasksHelper.TaskMatcher;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link DomainTransferRequestFlow} that use the old fee extensions (0.6, 0.11, 0.12).
|
||||
*/
|
||||
public class DomainTransferRequestFlowOldFeeExtensionsTest
|
||||
extends ProductionSimulatingFeeExtensionsTest<DomainTransferRequestFlow> {
|
||||
|
||||
private static final ImmutableMap<String, String> BASE_FEE_MAP =
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.put("DOMAIN", "example.tld")
|
||||
.put("YEARS", "1")
|
||||
.put("AMOUNT", "11.00")
|
||||
.put("CURRENCY", "USD")
|
||||
.build();
|
||||
private static final ImmutableMap<String, String> FEE_06_MAP =
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.putAll(BASE_FEE_MAP)
|
||||
.put("FEE_VERSION", "fee-0.6")
|
||||
.put("FEE_NS", "fee")
|
||||
.build();
|
||||
private static final ImmutableMap<String, String> FEE_11_MAP =
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.putAll(BASE_FEE_MAP)
|
||||
.put("FEE_VERSION", "fee-0.11")
|
||||
.put("FEE_NS", "fee11")
|
||||
.build();
|
||||
private static final ImmutableMap<String, String> FEE_12_MAP =
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.putAll(BASE_FEE_MAP)
|
||||
.put("FEE_VERSION", "fee-0.12")
|
||||
.put("FEE_NS", "fee12")
|
||||
.build();
|
||||
private static final ImmutableMap<String, String> RICH_DOMAIN_MAP =
|
||||
ImmutableMap.<String, String>builder()
|
||||
.put("DOMAIN", "rich.example")
|
||||
.put("YEARS", "1")
|
||||
.put("AMOUNT", "100.00")
|
||||
.put("CURRENCY", "USD")
|
||||
.put("FEE_VERSION", "fee-0.12")
|
||||
.put("FEE_NS", "fee12")
|
||||
.build();
|
||||
|
||||
private static final DateTime TRANSFER_REQUEST_TIME = DateTime.parse("2000-06-06T22:00:00.0Z");
|
||||
private static final DateTime TRANSFER_EXPIRATION_TIME =
|
||||
TRANSFER_REQUEST_TIME.plus(Tld.DEFAULT_AUTOMATIC_TRANSFER_LENGTH);
|
||||
private static final Duration TIME_SINCE_REQUEST = Duration.standardDays(3);
|
||||
private static final int EXTENDED_REGISTRATION_YEARS = 1;
|
||||
private static final DateTime REGISTRATION_EXPIRATION_TIME =
|
||||
DateTime.parse("2001-09-08T22:00:00.0Z");
|
||||
private static final DateTime EXTENDED_REGISTRATION_EXPIRATION_TIME =
|
||||
REGISTRATION_EXPIRATION_TIME.plusYears(EXTENDED_REGISTRATION_YEARS);
|
||||
|
||||
private Contact contact;
|
||||
private Domain domain;
|
||||
private Host subordinateHost;
|
||||
private DomainHistory historyEntryDomainCreate;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEachDomainTransferRequestFlowOldFeeExtensionsTest() {
|
||||
setEppInput("domain_transfer_request.xml");
|
||||
setRegistrarIdForFlow("NewRegistrar");
|
||||
clock.setTo(TRANSFER_REQUEST_TIME.plus(TIME_SINCE_REQUEST));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v06() {
|
||||
setupDomain("example", "tld");
|
||||
runWrongFeeAmountTest(FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v11() {
|
||||
setupDomain("example", "tld");
|
||||
runWrongFeeAmountTest(FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongFeeAmount_v12() {
|
||||
setupDomain("example", "tld");
|
||||
runWrongFeeAmountTest(FEE_12_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v06() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
UnsupportedFeeAttributeException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_applied.xml", FEE_06_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v11() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
UnsupportedFeeAttributeException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_applied.xml", FEE_11_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_appliedFee_v12() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
UnsupportedFeeAttributeException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_applied.xml", FEE_12_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v06() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
UnsupportedFeeAttributeException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_grace_period.xml", FEE_06_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v11() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
UnsupportedFeeAttributeException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_grace_period.xml", FEE_11_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_gracePeriodFee_v12() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
UnsupportedFeeAttributeException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_grace_period.xml", FEE_12_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v06() throws Exception {
|
||||
setupDomain("example", "tld");
|
||||
doSuccessfulTest(
|
||||
"domain_transfer_request_fee_defaults.xml",
|
||||
"domain_transfer_request_response_fee.xml",
|
||||
FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v11() throws Exception {
|
||||
setupDomain("example", "tld");
|
||||
doSuccessfulTest(
|
||||
"domain_transfer_request_fee_defaults.xml",
|
||||
"domain_transfer_request_response_fee.xml",
|
||||
FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_withDefaultAttributes_v12() throws Exception {
|
||||
setupDomain("example", "tld");
|
||||
doSuccessfulTest(
|
||||
"domain_transfer_request_fee_defaults.xml",
|
||||
"domain_transfer_request_response_fee.xml",
|
||||
FEE_12_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v06() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
UnsupportedFeeAttributeException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_refundable.xml", FEE_06_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v11() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
UnsupportedFeeAttributeException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_refundable.xml", FEE_11_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_refundableFee_v12() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
UnsupportedFeeAttributeException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_refundable.xml", FEE_12_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v06() {
|
||||
runWrongCurrencyTest(FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v11() {
|
||||
runWrongCurrencyTest(FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_wrongCurrency_v12() {
|
||||
runWrongCurrencyTest(FEE_12_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v06() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
CurrencyValueScaleException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_bad_scale.xml", FEE_06_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v11() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
CurrencyValueScaleException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_bad_scale.xml", FEE_11_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_feeGivenInWrongScale_v12() {
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
CurrencyValueScaleException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee_bad_scale.xml", FEE_12_MAP));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v06() throws Exception {
|
||||
setupDomain("example", "tld");
|
||||
doSuccessfulTest(
|
||||
"domain_transfer_request_fee.xml", "domain_transfer_request_response_fee.xml", FEE_06_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v11() throws Exception {
|
||||
setupDomain("example", "tld");
|
||||
doSuccessfulTest(
|
||||
"domain_transfer_request_fee.xml", "domain_transfer_request_response_fee.xml", FEE_11_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fee_v12() throws Exception {
|
||||
setupDomain("example", "tld");
|
||||
doSuccessfulTest(
|
||||
"domain_transfer_request_fee.xml", "domain_transfer_request_response_fee.xml", FEE_12_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_customLogicFee_v06() throws Exception {
|
||||
setupDomain("expensive-domain", "foo");
|
||||
clock.advanceOneMilli();
|
||||
doSuccessfulTest(
|
||||
"domain_transfer_request_separate_fees.xml",
|
||||
"domain_transfer_request_response_fees.xml",
|
||||
domain.getRegistrationExpirationTime().plusYears(1),
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.put("DOMAIN", "expensive-domain.foo")
|
||||
.put("YEARS", "1")
|
||||
.put("AMOUNT", "111.00")
|
||||
.put("EXDATE", "2002-09-08T22:00:00.0Z")
|
||||
.put("FEE_VERSION", "fee-0.6")
|
||||
.put("FEE_NS", "fee")
|
||||
.build(),
|
||||
Optional.of(Money.of(USD, 111)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_premiumNotBlocked_v12() throws Exception {
|
||||
setupDomain("rich", "example");
|
||||
clock.advanceOneMilli();
|
||||
// We don't verify the results; just check that the flow doesn't fail.
|
||||
runTest("domain_transfer_request_fee.xml", UserPrivileges.NORMAL, RICH_DOMAIN_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_superuserExtension_zeroPeriod_feeTransferExtension_v12() {
|
||||
setupDomain("example", "tld");
|
||||
eppRequestSource = EppRequestSource.TOOL;
|
||||
clock.advanceOneMilli();
|
||||
assertThrows(
|
||||
TransferPeriodZeroAndFeeTransferExtensionException.class,
|
||||
() ->
|
||||
runTest(
|
||||
"domain_transfer_request_fee_and_superuser_extension.xml",
|
||||
UserPrivileges.SUPERUSER,
|
||||
new ImmutableMap.Builder<String, String>()
|
||||
.putAll(FEE_12_MAP)
|
||||
.put("PERIOD", "0")
|
||||
.put("AUTOMATIC_TRANSFER_LENGTH", "5")
|
||||
.build()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_premiumNotBlockedInSuperuserMode_v12() throws Exception {
|
||||
setupDomain("rich", "example");
|
||||
clock.advanceOneMilli();
|
||||
// Modify the Registrar to block premium names.
|
||||
persistResource(loadRegistrar("NewRegistrar").asBuilder().setBlockPremiumNames(true).build());
|
||||
// We don't verify the results; just check that the flow doesn't fail.
|
||||
runTest("domain_transfer_request_fee.xml", UserPrivileges.SUPERUSER, RICH_DOMAIN_MAP);
|
||||
}
|
||||
|
||||
private void runWrongCurrencyTest(Map<String, String> substitutions) {
|
||||
Map<String, String> fullSubstitutions = Maps.newHashMap();
|
||||
fullSubstitutions.putAll(substitutions);
|
||||
fullSubstitutions.put("CURRENCY", "EUR");
|
||||
setupDomain("example", "tld");
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
CurrencyUnitMismatchException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee.xml", fullSubstitutions));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a successful test. The extraExpectedBillingEvents parameter consists of cancellation
|
||||
* billing event builders that have had all of their attributes set except for the parent history
|
||||
* entry, which is filled in during the execution of this method.
|
||||
*/
|
||||
private void doSuccessfulTest(
|
||||
String commandFilename,
|
||||
String expectedXmlFilename,
|
||||
DateTime expectedExpirationTime,
|
||||
Map<String, String> substitutions,
|
||||
Optional<Money> transferCost,
|
||||
BillingCancellation.Builder... extraExpectedBillingEvents)
|
||||
throws Exception {
|
||||
setEppInput(commandFilename, substitutions);
|
||||
ImmutableSet<GracePeriod> originalGracePeriods = domain.getGracePeriods();
|
||||
// Replace the ROID in the xml file with the one generated in our test.
|
||||
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
|
||||
// For all of the other transfer flow tests, 'now' corresponds to day 3 of the transfer, but
|
||||
// for the request test we want that same 'now' to be the initial request time, so we shift
|
||||
// the transfer timeline 3 days later by adjusting the implicit transfer time here.
|
||||
Tld registry = Tld.get(domain.getTld());
|
||||
DateTime implicitTransferTime = clock.nowUtc().plus(registry.getAutomaticTransferLength());
|
||||
// Setup done; run the test.
|
||||
assertMutatingFlow(true);
|
||||
runFlowAssertResponse(loadFile(expectedXmlFilename, substitutions));
|
||||
// Transfer should have been requested.
|
||||
domain = reloadResourceByForeignKey();
|
||||
// Verify that HistoryEntry was created.
|
||||
assertAboutDomains()
|
||||
.that(domain)
|
||||
.hasOneHistoryEntryEachOfTypes(DOMAIN_CREATE, DOMAIN_TRANSFER_REQUEST);
|
||||
assertLastHistoryContainsResource(domain);
|
||||
final HistoryEntry historyEntryTransferRequest =
|
||||
getOnlyHistoryEntryOfType(domain, DOMAIN_TRANSFER_REQUEST);
|
||||
assertAboutHistoryEntries()
|
||||
.that(historyEntryTransferRequest)
|
||||
.hasPeriodYears(1)
|
||||
.and()
|
||||
.hasOtherRegistrarId("TheRegistrar");
|
||||
// Verify correct fields were set.
|
||||
assertTransferRequested(
|
||||
domain, implicitTransferTime, Period.create(1, Unit.YEARS), expectedExpirationTime);
|
||||
|
||||
subordinateHost = reloadResourceAndCloneAtTime(subordinateHost, clock.nowUtc());
|
||||
assertAboutHosts().that(subordinateHost).hasNoHistoryEntries();
|
||||
|
||||
assertHistoryEntriesContainBillingEventsAndGracePeriods(
|
||||
expectedExpirationTime,
|
||||
implicitTransferTime,
|
||||
transferCost,
|
||||
originalGracePeriods,
|
||||
/* expectTransferBillingEvent= */ true,
|
||||
extraExpectedBillingEvents);
|
||||
|
||||
assertPollMessagesEmitted(expectedExpirationTime, implicitTransferTime);
|
||||
assertAboutDomainAfterAutomaticTransfer(
|
||||
expectedExpirationTime, implicitTransferTime, Period.create(1, Unit.YEARS));
|
||||
cloudTasksHelper.assertTasksEnqueued(
|
||||
QUEUE_ASYNC_ACTIONS,
|
||||
new TaskMatcher()
|
||||
.path(ResaveEntityAction.PATH)
|
||||
.method(HttpMethod.POST)
|
||||
.service("backend")
|
||||
.header("content-type", "application/x-www-form-urlencoded")
|
||||
.param(PARAM_RESOURCE_KEY, domain.createVKey().stringify())
|
||||
.param(PARAM_REQUESTED_TIME, clock.nowUtc().toString())
|
||||
.scheduleTime(clock.nowUtc().plus(registry.getAutomaticTransferLength())));
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(
|
||||
String commandFilename, String expectedXmlFilename, Map<String, String> substitutions)
|
||||
throws Exception {
|
||||
clock.advanceOneMilli();
|
||||
doSuccessfulTest(
|
||||
commandFilename,
|
||||
expectedXmlFilename,
|
||||
domain.getRegistrationExpirationTime().plusYears(1),
|
||||
substitutions,
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(String commandFilename, String expectedXmlFilename)
|
||||
throws Exception {
|
||||
clock.advanceOneMilli();
|
||||
doSuccessfulTest(
|
||||
commandFilename, expectedXmlFilename, domain.getRegistrationExpirationTime().plusYears(1));
|
||||
}
|
||||
|
||||
private void doSuccessfulTest(
|
||||
String commandFilename,
|
||||
String expectedXmlFilename,
|
||||
DateTime expectedExpirationTime,
|
||||
BillingCancellation.Builder... extraExpectedBillingEvents)
|
||||
throws Exception {
|
||||
doSuccessfulTest(
|
||||
commandFilename,
|
||||
expectedXmlFilename,
|
||||
expectedExpirationTime,
|
||||
ImmutableMap.of(),
|
||||
Optional.empty(),
|
||||
extraExpectedBillingEvents);
|
||||
}
|
||||
|
||||
private void runWrongFeeAmountTest(Map<String, String> substitutions) {
|
||||
persistResource(
|
||||
Tld.get("tld")
|
||||
.asBuilder()
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 20)))
|
||||
.build());
|
||||
EppException thrown =
|
||||
assertThrows(
|
||||
FeesMismatchException.class,
|
||||
() -> doFailingTest("domain_transfer_request_fee.xml", substitutions));
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
}
|
||||
|
||||
private void runTest(
|
||||
String commandFilename, UserPrivileges userPrivileges, Map<String, String> substitutions)
|
||||
throws Exception {
|
||||
setEppInput(commandFilename, substitutions);
|
||||
// Replace the ROID in the xml file with the one generated in our test.
|
||||
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
|
||||
// Setup done; run the test.
|
||||
assertMutatingFlow(true);
|
||||
runFlow(CommitMode.LIVE, userPrivileges);
|
||||
}
|
||||
|
||||
private void runTest(String commandFilename, UserPrivileges userPrivileges) throws Exception {
|
||||
runTest(commandFilename, userPrivileges, ImmutableMap.of());
|
||||
}
|
||||
|
||||
private void doFailingTest(String commandFilename, Map<String, String> substitutions)
|
||||
throws Exception {
|
||||
runTest(commandFilename, UserPrivileges.NORMAL, substitutions);
|
||||
}
|
||||
|
||||
private void doFailingTest(String commandFilename) throws Exception {
|
||||
runTest(commandFilename, UserPrivileges.NORMAL, ImmutableMap.of());
|
||||
}
|
||||
|
||||
/** Adds a domain with no pending transfer on it. */
|
||||
void setupDomain(String label, String tld) {
|
||||
createTld(tld);
|
||||
contact = persistActiveContact("jd1234");
|
||||
domain =
|
||||
persistDomainWithDependentResources(
|
||||
label,
|
||||
tld,
|
||||
contact,
|
||||
clock.nowUtc(),
|
||||
DateTime.parse("1999-04-03T22:00:00.0Z"),
|
||||
REGISTRATION_EXPIRATION_TIME);
|
||||
subordinateHost =
|
||||
persistResource(
|
||||
new Host.Builder()
|
||||
.setRepoId("2-".concat(Ascii.toUpperCase(tld)))
|
||||
.setHostName("ns1." + label + "." + tld)
|
||||
.setPersistedCurrentSponsorRegistrarId("TheRegistrar")
|
||||
.setCreationRegistrarId("TheRegistrar")
|
||||
.setCreationTimeForTest(DateTime.parse("1999-04-03T22:00:00.0Z"))
|
||||
.setSuperordinateDomain(domain.createVKey())
|
||||
.build());
|
||||
domain =
|
||||
persistResource(
|
||||
domain.asBuilder().addSubordinateHost(subordinateHost.getHostName()).build());
|
||||
historyEntryDomainCreate =
|
||||
getOnlyHistoryEntryOfType(domain, DOMAIN_CREATE, DomainHistory.class);
|
||||
}
|
||||
|
||||
private void assertPollMessagesEmitted(
|
||||
DateTime expectedExpirationTime, DateTime implicitTransferTime) {
|
||||
// Assert that there exists a poll message to notify the losing registrar that a transfer was
|
||||
// requested. If the implicit transfer time is now (i.e. the automatic transfer length is zero)
|
||||
// then also expect a server approved poll message.
|
||||
assertThat(getPollMessages("TheRegistrar", clock.nowUtc()))
|
||||
.hasSize(implicitTransferTime.equals(clock.nowUtc()) ? 2 : 1);
|
||||
|
||||
// Two poll messages on the gaining registrar's side at the expected expiration time: a
|
||||
// (OneTime) transfer approved message, and an Autorenew poll message.
|
||||
assertThat(getPollMessages("NewRegistrar", expectedExpirationTime)).hasSize(2);
|
||||
PollMessage transferApprovedPollMessage =
|
||||
getOnlyPollMessage("NewRegistrar", implicitTransferTime, PollMessage.OneTime.class);
|
||||
PollMessage autorenewPollMessage =
|
||||
getOnlyPollMessage("NewRegistrar", expectedExpirationTime, PollMessage.Autorenew.class);
|
||||
assertThat(transferApprovedPollMessage.getEventTime()).isEqualTo(implicitTransferTime);
|
||||
assertThat(autorenewPollMessage.getEventTime()).isEqualTo(expectedExpirationTime);
|
||||
assertThat(
|
||||
transferApprovedPollMessage.getResponseData().stream()
|
||||
.filter(TransferResponse.class::isInstance)
|
||||
.map(TransferResponse.class::cast)
|
||||
.collect(onlyElement())
|
||||
.getTransferStatus())
|
||||
.isEqualTo(TransferStatus.SERVER_APPROVED);
|
||||
PendingActionNotificationResponse panData =
|
||||
transferApprovedPollMessage.getResponseData().stream()
|
||||
.filter(PendingActionNotificationResponse.class::isInstance)
|
||||
.map(PendingActionNotificationResponse.class::cast)
|
||||
.collect(onlyElement());
|
||||
assertThat(panData.getTrid().getClientTransactionId()).hasValue("ABC-12345");
|
||||
assertThat(panData.getActionResult()).isTrue();
|
||||
|
||||
// Two poll messages on the losing registrar's side at the implicit transfer time: a
|
||||
// transfer pending message, and a transfer approved message (both OneTime messages).
|
||||
assertThat(getPollMessages("TheRegistrar", implicitTransferTime)).hasSize(2);
|
||||
PollMessage losingTransferPendingPollMessage =
|
||||
getPollMessages("TheRegistrar", clock.nowUtc()).stream()
|
||||
.filter(pollMessage -> TransferStatus.PENDING.getMessage().equals(pollMessage.getMsg()))
|
||||
.collect(onlyElement());
|
||||
PollMessage losingTransferApprovedPollMessage =
|
||||
getPollMessages("TheRegistrar", implicitTransferTime).stream()
|
||||
.filter(Predicates.not(Predicates.equalTo(losingTransferPendingPollMessage)))
|
||||
.collect(onlyElement());
|
||||
assertThat(losingTransferPendingPollMessage.getEventTime()).isEqualTo(clock.nowUtc());
|
||||
assertThat(losingTransferApprovedPollMessage.getEventTime()).isEqualTo(implicitTransferTime);
|
||||
assertThat(
|
||||
losingTransferPendingPollMessage.getResponseData().stream()
|
||||
.filter(TransferResponse.class::isInstance)
|
||||
.map(TransferResponse.class::cast)
|
||||
.collect(onlyElement())
|
||||
.getTransferStatus())
|
||||
.isEqualTo(TransferStatus.PENDING);
|
||||
assertThat(
|
||||
losingTransferApprovedPollMessage.getResponseData().stream()
|
||||
.filter(TransferResponse.class::isInstance)
|
||||
.map(TransferResponse.class::cast)
|
||||
.collect(onlyElement())
|
||||
.getTransferStatus())
|
||||
.isEqualTo(TransferStatus.SERVER_APPROVED);
|
||||
|
||||
// Assert that the poll messages show up in the TransferData server approve entities.
|
||||
assertPollMessagesEqual(
|
||||
loadByKey(domain.getTransferData().getServerApproveAutorenewPollMessage()),
|
||||
autorenewPollMessage);
|
||||
// Assert that the full set of server-approve poll messages is exactly the server approve
|
||||
// OneTime messages to gaining and losing registrars plus the gaining client autorenew.
|
||||
assertPollMessagesEqual(
|
||||
Iterables.filter(
|
||||
loadByKeys(domain.getTransferData().getServerApproveEntities()), PollMessage.class),
|
||||
ImmutableList.of(
|
||||
transferApprovedPollMessage, losingTransferApprovedPollMessage, autorenewPollMessage));
|
||||
}
|
||||
|
||||
private void assertHistoryEntriesContainBillingEventsAndGracePeriods(
|
||||
DateTime expectedExpirationTime,
|
||||
DateTime implicitTransferTime,
|
||||
Optional<Money> transferCost,
|
||||
ImmutableSet<GracePeriod> originalGracePeriods,
|
||||
boolean expectTransferBillingEvent,
|
||||
BillingCancellation.Builder... extraExpectedBillingEvents) {
|
||||
Tld registry = Tld.get(domain.getTld());
|
||||
final DomainHistory historyEntryTransferRequest =
|
||||
getOnlyHistoryEntryOfType(domain, DOMAIN_TRANSFER_REQUEST, DomainHistory.class);
|
||||
|
||||
// Construct the billing events we expect to exist, starting with the (optional) billing
|
||||
// event for the transfer itself.
|
||||
Optional<BillingEvent> optionalTransferBillingEvent;
|
||||
if (expectTransferBillingEvent) {
|
||||
// For normal transfers, a BillingEvent should be created AUTOMATIC_TRANSFER_DAYS in the
|
||||
// future, for the case when the transfer is implicitly acked.
|
||||
optionalTransferBillingEvent =
|
||||
Optional.of(
|
||||
new BillingEvent.Builder()
|
||||
.setReason(Reason.TRANSFER)
|
||||
.setTargetId(domain.getDomainName())
|
||||
.setEventTime(implicitTransferTime)
|
||||
.setBillingTime(
|
||||
implicitTransferTime.plus(registry.getTransferGracePeriodLength()))
|
||||
.setRegistrarId("NewRegistrar")
|
||||
.setCost(transferCost.orElse(Money.of(USD, 11)))
|
||||
.setPeriodYears(1)
|
||||
.setDomainHistory(historyEntryTransferRequest)
|
||||
.build());
|
||||
} else {
|
||||
// Superuser transfers with no bundled renewal have no transfer billing event.
|
||||
optionalTransferBillingEvent = Optional.empty();
|
||||
}
|
||||
// Construct the autorenew events for the losing/existing client and the gaining one. Note that
|
||||
// all of the other transfer flow tests happen on day 3 of the transfer, but the initial
|
||||
// request by definition takes place on day 1, so we need to edit the times in the
|
||||
// autorenew events from the base test case.
|
||||
BillingRecurrence losingClientAutorenew =
|
||||
getLosingClientAutorenewEvent()
|
||||
.asBuilder()
|
||||
.setRecurrenceEndTime(implicitTransferTime)
|
||||
.build();
|
||||
BillingRecurrence gainingClientAutorenew =
|
||||
getGainingClientAutorenewEvent()
|
||||
.asBuilder()
|
||||
.setEventTime(expectedExpirationTime)
|
||||
.setRecurrenceLastExpansion(expectedExpirationTime.minusYears(1))
|
||||
.build();
|
||||
// Construct extra billing events expected by the specific test.
|
||||
ImmutableSet<BillingBase> extraBillingBases =
|
||||
Stream.of(extraExpectedBillingEvents)
|
||||
.map(builder -> builder.setDomainHistory(historyEntryTransferRequest).build())
|
||||
.collect(toImmutableSet());
|
||||
// Assert that the billing events we constructed above actually exist in the database.
|
||||
ImmutableSet<BillingBase> expectedBillingBases =
|
||||
Streams.concat(
|
||||
Stream.of(losingClientAutorenew, gainingClientAutorenew),
|
||||
optionalTransferBillingEvent.stream())
|
||||
.collect(toImmutableSet());
|
||||
assertBillingEvents(Sets.union(expectedBillingBases, extraBillingBases));
|
||||
// Assert that the domain's TransferData server-approve billing events match the above.
|
||||
if (expectTransferBillingEvent) {
|
||||
assertBillingEventsEqual(
|
||||
loadByKey(domain.getTransferData().getServerApproveBillingEvent()),
|
||||
optionalTransferBillingEvent.get());
|
||||
} else {
|
||||
assertThat(domain.getTransferData().getServerApproveBillingEvent()).isNull();
|
||||
}
|
||||
assertBillingEventsEqual(
|
||||
loadByKey(domain.getTransferData().getServerApproveAutorenewEvent()),
|
||||
gainingClientAutorenew);
|
||||
// Assert that the full set of server-approve billing events is exactly the extra ones plus
|
||||
// the transfer billing event (if present) and the gaining client autorenew.
|
||||
ImmutableSet<BillingBase> expectedServeApproveBillingBases =
|
||||
Streams.concat(Stream.of(gainingClientAutorenew), optionalTransferBillingEvent.stream())
|
||||
.collect(toImmutableSet());
|
||||
assertBillingEventsEqual(
|
||||
Iterables.filter(
|
||||
loadByKeys(domain.getTransferData().getServerApproveEntities()), BillingBase.class),
|
||||
Sets.union(expectedServeApproveBillingBases, extraBillingBases));
|
||||
// The domain's autorenew billing event should still point to the losing client's event.
|
||||
BillingRecurrence domainAutorenewEvent = loadByKey(domain.getAutorenewBillingEvent());
|
||||
assertThat(domainAutorenewEvent.getRegistrarId()).isEqualTo("TheRegistrar");
|
||||
assertThat(domainAutorenewEvent.getRecurrenceEndTime()).isEqualTo(implicitTransferTime);
|
||||
// The original grace periods should remain untouched.
|
||||
assertThat(domain.getGracePeriods()).containsExactlyElementsIn(originalGracePeriods);
|
||||
// If we fast forward AUTOMATIC_TRANSFER_DAYS, the transfer should have cleared out all other
|
||||
// grace periods, but expect a transfer grace period (if there was a transfer billing event).
|
||||
Domain domainAfterAutomaticTransfer = domain.cloneProjectedAtTime(implicitTransferTime);
|
||||
if (expectTransferBillingEvent) {
|
||||
assertGracePeriods(
|
||||
domainAfterAutomaticTransfer.getGracePeriods(),
|
||||
ImmutableMap.of(
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.TRANSFER,
|
||||
domain.getRepoId(),
|
||||
implicitTransferTime.plus(registry.getTransferGracePeriodLength()),
|
||||
"NewRegistrar",
|
||||
null),
|
||||
optionalTransferBillingEvent.get()));
|
||||
} else {
|
||||
assertGracePeriods(domainAfterAutomaticTransfer.getGracePeriods(), ImmutableMap.of());
|
||||
}
|
||||
}
|
||||
|
||||
private BillingRecurrence getGainingClientAutorenewEvent() {
|
||||
return new BillingRecurrence.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
.setTargetId(domain.getDomainName())
|
||||
.setRegistrarId("NewRegistrar")
|
||||
.setEventTime(EXTENDED_REGISTRATION_EXPIRATION_TIME)
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setDomainHistory(
|
||||
getOnlyHistoryEntryOfType(
|
||||
domain, HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST, DomainHistory.class))
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Get the autorenew event that the losing client will have after a SERVER_APPROVED transfer. */
|
||||
private BillingRecurrence getLosingClientAutorenewEvent() {
|
||||
return new BillingRecurrence.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
.setTargetId(domain.getDomainName())
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setEventTime(REGISTRATION_EXPIRATION_TIME)
|
||||
.setRecurrenceEndTime(TRANSFER_EXPIRATION_TIME)
|
||||
.setDomainHistory(historyEntryDomainCreate)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void assertAboutDomainAfterAutomaticTransfer(
|
||||
DateTime expectedExpirationTime, DateTime implicitTransferTime, Period expectedPeriod)
|
||||
throws Exception {
|
||||
Tld registry = Tld.get(domain.getTld());
|
||||
Domain domainAfterAutomaticTransfer = domain.cloneProjectedAtTime(implicitTransferTime);
|
||||
assertTransferApproved(domainAfterAutomaticTransfer, implicitTransferTime, expectedPeriod);
|
||||
assertAboutDomains()
|
||||
.that(domainAfterAutomaticTransfer)
|
||||
.hasRegistrationExpirationTime(expectedExpirationTime)
|
||||
.and()
|
||||
.hasLastEppUpdateTime(implicitTransferTime)
|
||||
.and()
|
||||
.hasLastEppUpdateRegistrarId("NewRegistrar");
|
||||
assertThat(loadByKey(domainAfterAutomaticTransfer.getAutorenewBillingEvent()).getEventTime())
|
||||
.isEqualTo(expectedExpirationTime);
|
||||
// And after the expected grace time, the grace period should be gone.
|
||||
Domain afterGracePeriod =
|
||||
domain.cloneProjectedAtTime(
|
||||
clock
|
||||
.nowUtc()
|
||||
.plus(registry.getAutomaticTransferLength())
|
||||
.plus(registry.getTransferGracePeriodLength()));
|
||||
assertThat(afterGracePeriod.getGracePeriods()).isEmpty();
|
||||
}
|
||||
|
||||
private void assertTransferApproved(
|
||||
Domain domain, DateTime automaticTransferTime, Period expectedPeriod) throws Exception {
|
||||
assertAboutDomains()
|
||||
.that(domain)
|
||||
.hasCurrentSponsorRegistrarId("NewRegistrar")
|
||||
.and()
|
||||
.hasLastTransferTime(automaticTransferTime)
|
||||
.and()
|
||||
.doesNotHaveStatusValue(StatusValue.PENDING_TRANSFER);
|
||||
Trid expectedTrid =
|
||||
Trid.create(
|
||||
getClientTrid(),
|
||||
domain.getTransferData().getTransferRequestTrid().getServerTransactionId());
|
||||
assertThat(domain.getTransferData())
|
||||
.isEqualTo(
|
||||
new DomainTransferData.Builder()
|
||||
.setGainingRegistrarId("NewRegistrar")
|
||||
.setLosingRegistrarId("TheRegistrar")
|
||||
.setTransferRequestTrid(expectedTrid)
|
||||
.setTransferRequestTime(clock.nowUtc())
|
||||
.setTransferPeriod(expectedPeriod)
|
||||
.setTransferStatus(TransferStatus.SERVER_APPROVED)
|
||||
.setPendingTransferExpirationTime(automaticTransferTime)
|
||||
.setTransferredRegistrationExpirationTime(domain.getRegistrationExpirationTime())
|
||||
// Server-approve entity fields should all be nulled out.
|
||||
.build());
|
||||
}
|
||||
|
||||
private void assertTransferRequested(
|
||||
Domain domain,
|
||||
DateTime automaticTransferTime,
|
||||
Period expectedPeriod,
|
||||
DateTime expectedExpirationTime)
|
||||
throws Exception {
|
||||
assertAboutDomains()
|
||||
.that(domain)
|
||||
.hasCurrentSponsorRegistrarId("TheRegistrar")
|
||||
.and()
|
||||
.hasStatusValue(StatusValue.PENDING_TRANSFER)
|
||||
.and()
|
||||
.hasLastEppUpdateTime(clock.nowUtc())
|
||||
.and()
|
||||
.hasLastEppUpdateRegistrarId("NewRegistrar");
|
||||
Trid expectedTrid =
|
||||
Trid.create(
|
||||
getClientTrid(),
|
||||
domain.getTransferData().getTransferRequestTrid().getServerTransactionId());
|
||||
assertThat(domain.getTransferData())
|
||||
.isEqualTo(
|
||||
// Compare against only the following fields by rebuilding the existing TransferData.
|
||||
// Equivalent to assertThat(transferData.getGainingClientId()).isEqualTo("NewReg")
|
||||
// and similar individual assertions, but produces a nicer error message this way.
|
||||
domain
|
||||
.getTransferData()
|
||||
.asBuilder()
|
||||
.setGainingRegistrarId("NewRegistrar")
|
||||
.setLosingRegistrarId("TheRegistrar")
|
||||
.setTransferRequestTrid(expectedTrid)
|
||||
.setTransferRequestTime(clock.nowUtc())
|
||||
.setTransferPeriod(expectedPeriod)
|
||||
.setTransferStatus(TransferStatus.PENDING)
|
||||
.setPendingTransferExpirationTime(automaticTransferTime)
|
||||
.setTransferredRegistrationExpirationTime(expectedExpirationTime)
|
||||
// Don't compare the server-approve entity fields; they're hard to reconstruct
|
||||
// and logic later will check them.
|
||||
.build());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user