1
0
mirror of https://github.com/google/nomulus synced 2026-02-01 10:32:19 +00:00

Compare commits

..

20 Commits

Author SHA1 Message Date
gbrodman
3f8145b44f Remove various ContactHistory references (#2949)
This keeps the ContactHistory class and tests, to avoid changing any
database-related code in this PR.
2026-01-29 21:42:59 +00:00
gbrodman
1fdacf25dc Remove pipeline/action to wipe out contact data (#2948)
We've wiped it all out now, so it's moot
2026-01-29 19:38:29 +00:00
gbrodman
41d26d8385 Remove references to contacts in domain flows (#2944)
We've moved on from contacts entirely now so the only thing we really
need to do is make sure that people don't include contacts in domain
creates or updates. This also makes auth code checking easier too,
because now the only auth code that you're allowed to provide is the
domain auth code (not a contact auth code)
2026-01-29 19:30:41 +00:00
Nilay Shah
71c9407f07 Add MosApiMetrics exporter (#2931)
* Add MosApiMetrics exporter with status code mapping

Introduces the metrics exporter for the MoSAPI system.

- Implements `MosApiMetrics` to export TLD and service states to Cloud Monitoring.
- Maps ICANN status codes to numeric gauges: 1 (UP), 0 (DOWN), and 2 (DISABLED/INCONCLUSIVE).
- Sets `MAX_TIMESERIES_PER_REQUEST` to 195 to respect Cloud Monitoring API limits

* Automate metric descriptor creation on startup in Cloud Monitoring

* Refactor MoSAPI metrics for resilience and standards

* Refactor and nits

- Kept projectName as part constant instead of inside method signature
- Added Summary logs for metrics execution
- Metric Executor defaults to Single Threaded

* junit test refactoring

* Fix Metric kind to GAUGE for all metrics

* Refactor MosApiMetrics to remove async ExecutorService

* Add LockHandler for Metric Descriptor creation

* Update LockHandler lease time to one hour and refactoring
2026-01-29 14:53:05 +00:00
gbrodman
a138806199 Re-enable old fee extensions in sandbox (#2939)
Now that we've passed the RST testing (or at least the EPP portion of
it) we are no longer bound by the restriction to only use the fee
extension version 1.0 on sandbox.

For now, in order to avoid changing prod behavior, this does not enable
advertisement of the fee extension version 1.0 in production. We can
change this at any point in the future.
2026-01-21 21:49:29 +00:00
Juan Celhay
a5c1412aac Collect JVM memory metrics (#2937)
* add jvm metrics

* include all changes

* Fix tests and lint errors

* Fix formatting

* Instantiate jvmmetrics class in stackdriver module

* add metrics registration behaviour and explicit call

* redo tests

* fix formatting/variable name

* lint
2026-01-21 21:27:07 +00:00
Nilay Shah
41393e5f8d Revert "Remove contact as a supported object type in EPP (#2932)" (#2938)
This reverts commit d8e647316e.
2026-01-21 18:35:07 +00:00
Ben McIlwain
a7387e975b Add RDAP nameserver tests for .zz-- TLD hostnames (#2936)
The actual error is fixed as a side effect of PR #2935, but this adds tests
verifying the intended behavior.

BUG= http://b/476144993
2026-01-16 17:55:41 +00:00
Ben McIlwain
5c6667507b Remove contacts from RDE (#2934)
This is necessary to pass RST, as we cannot have any mention of contacts in our
escrow files as we are a thin registry.

BUG= http://b/474636582
2026-01-16 15:25:33 +00:00
Ben McIlwain
c187c92ae4 Allow creation of hostnames on .zz-- style TLDs for RST (#2935)
This is a follow-on to PR #2909, which fixed the issue for domains, but
apparently not fully for hostnames.

BUG= http://b/476144993
2026-01-15 20:37:32 +00:00
gbrodman
22ca4e3f2b Disable old fee extensions in non-prod envs (#2933)
The primary annoyance with this is that it means we need (or at least,
should) split all tests that use the fee extension into two separate
tests -- one that simulates non-prod environments, and one that
simulates prod environments. This leads to duplication of many tests but
that's fine since this is theoretically temporary.
2026-01-14 19:04:22 +00:00
Nilay Shah
f27136458a Configure cloud scheduler to trigger MoSAPI SLA status to cloud monitoring (#2926)
* Configure cloud scheduler to trigger MoSAPI SLA status to cloud monitoring in production

- We have kept this job to trigger for every 3 minutes so that we get near to real time update for our task.
- This will not trigger metrics for now as we have not written Metrics triggering logic yet
- Logs are added

* Change Trigger scheduling from 3 minutes to 5 minutes
2026-01-13 18:48:43 +00:00
Ben McIlwain
d8e647316e Remove contact as a supported object type in EPP (#2932)
This primarily affects the EPP greeting. We already were erroring out when any
contact flows attempted to be run; this should just prevent registrars from even
trying them at all.

This PR is designed to be minimally invasive, and does not remove any of the
contact flows or Jakarta XML/XJC objects/files themselves. That can be done
later as a follow-up.

Also note that the contact namespace urn:ietf:params:xml:ns:contact-1.0 is still
present for now in RDE exports, but I'll remove that subsequently as well.

BUG= http://b/475506288
2026-01-13 17:21:03 +00:00
Ben McIlwain
d6e0a7b979 Change domain update commands to be varipotent by status (#2930)
This means that attempting to add a status that is already present will now
fail, and attempting to remove a status that is not present will also now fail.

This also refactors the existing checks into a single verify method, rather than
having to call three separate methods from every callsite.

BUG= http://b/474645068
2026-01-12 22:12:08 +00:00
Juan Celhay
5725eb95e0 Add Cloud java profiler to nomulus docker images (#2919)
* add cloud profiler to dockerfile and start script

* add apt-get update

* change in cb machine type for nomulus

* fix typo

* add max worker limit to gradle tests

* Switch to root before doing apt-get

* correct dockerfile

* jetty/Dockerfile

* profiler service conditional to kubernetes container name
2026-01-12 15:19:05 +00:00
Pavlo Tkach
aa12998276 Increase console workload memory allocation (#2929) 2026-01-09 19:27:07 +00:00
gbrodman
d415416bc5 Update the fee extension 1.0 and add some tests (#2925)
Many of the actual fee extension changes are based off Weimin's PR
https://github.com/google/nomulus/pull/2912, though this makes some
additional changes based on the XML schema and description from RFC 8748.

This adds tests for the DomainCheckFlow which is the most complex and
thorough user of the fee extension, but we'll want to add further tests
to the other domain flows to make sure they're handled correctly.
2026-01-09 18:09:17 +00:00
gbrodman
3a1068f313 Add indexes on current_package_token in Domain* (#2916)
It just makes it possible to delete allocation tokens, otherwise we need
to do a linear search over the entire Domain and DomainHistory tables if
we ever want to delete something.
2026-01-09 17:55:37 +00:00
gbrodman
69e5d40f04 Forbid no-op domain-NS and host-IP adds/removes (#2928)
The RST testing expects us to fail if they try to remove an IP from a
host that already doesn't that have that IP, or to add one that already
exists (ditto on both for a domain's nameservers). I don't really see an
issue with our previous no-op implementation, but we need to do this to
pass the tests.
2026-01-09 17:55:12 +00:00
gbrodman
64f6cd9af4 Only include fee 1.0 extension in nonprod envs (#2927)
We need to have this enabled in sandbox, but we wish to wait to enable
it for production to make sure that the implementation is correct and
that clients can use it.

Soon we'll want to do something similar (but the opposite) with the old
fee extensions, where we **only** serve them in production (or maybe
unit test as well). That will allow us to pass the RST tests that depend
on only having the fee extension 1.0.
2026-01-08 22:00:39 +00:00
263 changed files with 7296 additions and 4666 deletions

View File

@@ -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/

View File

@@ -569,11 +569,6 @@ if (environment == 'alpha') {
mainClass: 'google.registry.beam.resave.ResaveAllEppResourcesPipeline',
metaData: 'google/registry/beam/resave_all_epp_resources_pipeline_metadata.json'
],
wipeOutContactHistoryPii:
[
mainClass: 'google.registry.beam.wipeout.WipeOutContactHistoryPiiPipeline',
metaData: 'google/registry/beam/wipe_out_contact_history_pii_pipeline_metadata.json'
],
]
project.tasks.create("stageBeamPipelines") {
doLast {

View File

@@ -131,12 +131,6 @@ public class BatchModule {
return extractOptionalDatetimeParameter(req, ExpandBillingRecurrencesAction.PARAM_END_TIME);
}
@Provides
@Parameter(WipeOutContactHistoryPiiAction.PARAM_CUTOFF_TIME)
static Optional<DateTime> provideCutoffTime(HttpServletRequest req) {
return extractOptionalDatetimeParameter(req, WipeOutContactHistoryPiiAction.PARAM_CUTOFF_TIME);
}
@Provides
@Parameter(ExpandBillingRecurrencesAction.PARAM_ADVANCE_CURSOR)
static boolean provideAdvanceCursor(HttpServletRequest req) {

View File

@@ -1,238 +0,0 @@
// 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.batch;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static google.registry.flows.FlowUtils.marshalWithLenientRetry;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static java.nio.charset.StandardCharsets.US_ASCII;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.RateLimiter;
import google.registry.config.RegistryConfig.Config;
import google.registry.flows.EppController;
import google.registry.flows.EppRequestSource;
import google.registry.flows.PasswordOnlyTransportCredentials;
import google.registry.flows.StatelessRequestSessionMetadata;
import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.Domain;
import google.registry.model.eppcommon.ProtocolDefinition;
import google.registry.model.eppoutput.EppOutput;
import google.registry.persistence.VKey;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.request.lock.LockHandler;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import javax.annotation.Nullable;
import org.joda.time.Duration;
/**
* An action that removes all contacts from all active (non-deleted) domains.
*
* <p>This implements part 1 of phase 3 of the Minimum Dataset migration, wherein we remove all uses
* of contact objects in preparation for later removing all contact data from the system.
*
* <p>This runs as a singly threaded, resumable action that loads batches of domains still
* containing contacts, and runs a superuser domain update on each one to remove the contacts,
* leaving behind a record recording that update.
*/
@Action(
service = Action.Service.BACKEND,
path = RemoveAllDomainContactsAction.PATH,
method = Action.Method.POST,
auth = Auth.AUTH_ADMIN)
public class RemoveAllDomainContactsAction implements Runnable {
public static final String PATH = "/_dr/task/removeAllDomainContacts";
private static final String LOCK_NAME = "Remove all domain contacts";
private static final String CONTACT_FMT = "<domain:contact type=\"%s\">%s</domain:contact>";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final EppController eppController;
private final String registryAdminClientId;
private final LockHandler lockHandler;
private final RateLimiter rateLimiter;
private final Response response;
private final String updateDomainXml;
private int successes = 0;
private int failures = 0;
private static final int BATCH_SIZE = 10000;
@Inject
RemoveAllDomainContactsAction(
EppController eppController,
@Config("registryAdminClientId") String registryAdminClientId,
LockHandler lockHandler,
@Named("standardRateLimiter") RateLimiter rateLimiter,
Response response) {
this.eppController = eppController;
this.registryAdminClientId = registryAdminClientId;
this.lockHandler = lockHandler;
this.rateLimiter = rateLimiter;
this.response = response;
this.updateDomainXml =
readResourceUtf8(RemoveAllDomainContactsAction.class, "domain_remove_contacts.xml");
}
@Override
public void run() {
response.setContentType(PLAIN_TEXT_UTF_8);
Callable<Void> runner =
() -> {
try {
runLocked();
response.setStatus(SC_OK);
} catch (Exception e) {
logger.atSevere().withCause(e).log("Errored out during execution.");
response.setStatus(SC_INTERNAL_SERVER_ERROR);
response.setPayload(String.format("Errored out with cause: %s", e));
}
return null;
};
if (!lockHandler.executeWithLocks(runner, null, Duration.standardHours(1), LOCK_NAME)) {
// Send a 200-series status code to prevent this conflicting action from retrying.
response.setStatus(SC_NO_CONTENT);
response.setPayload("Could not acquire lock; already running?");
}
}
private void runLocked() {
logger.atInfo().log("Removing contacts on all active domains.");
List<String> domainRepoIdsBatch;
do {
domainRepoIdsBatch =
tm().<List<String>>transact(
() ->
tm().getEntityManager()
.createQuery(
"""
SELECT repoId FROM Domain WHERE deletionTime = :end_of_time AND NOT (
adminContact IS NULL AND billingContact IS NULL
AND registrantContact IS NULL AND techContact IS NULL)
""")
.setParameter("end_of_time", END_OF_TIME)
.setMaxResults(BATCH_SIZE)
.getResultList());
for (String domainRepoId : domainRepoIdsBatch) {
rateLimiter.acquire();
runDomainUpdateFlow(domainRepoId);
}
} while (!domainRepoIdsBatch.isEmpty());
String msg =
String.format(
"Finished; %d domains were successfully updated and %d errored out.",
successes, failures);
logger.at(failures == 0 ? Level.INFO : Level.WARNING).log(msg);
response.setPayload(msg);
}
private void runDomainUpdateFlow(String repoId) {
// Create a new transaction that the flow's execution will be enlisted in that loads the domain
// transactionally. This way we can ensure that nothing else has modified the domain in question
// in the intervening period since the query above found it. If a single domain update fails
// permanently, log it and move on to not block processing all the other domains.
try {
boolean success = tm().transact(() -> runDomainUpdateFlowInner(repoId));
if (success) {
successes++;
} else {
failures++;
}
} catch (Throwable t) {
logger.atWarning().withCause(t).log(
"Failed updating domain with repoId %s; skipping.", repoId);
}
}
/**
* Runs the actual domain update flow and returns whether the contact removals were successful.
*/
private boolean runDomainUpdateFlowInner(String repoId) {
Domain domain = tm().loadByKey(VKey.create(Domain.class, repoId));
if (!domain.getDeletionTime().equals(END_OF_TIME)) {
// Domain has been deleted since the action began running; nothing further to be
// done here.
logger.atInfo().log("Nothing to process for deleted domain '%s'.", domain.getDomainName());
return false;
}
logger.atInfo().log("Attempting to remove contacts on domain '%s'.", domain.getDomainName());
StringBuilder sb = new StringBuilder();
ImmutableMap<VKey<? extends Contact>, Contact> contacts =
tm().loadByKeys(
domain.getContacts().stream()
.map(DesignatedContact::getContactKey)
.collect(ImmutableSet.toImmutableSet()));
// Collect all the (non-registrant) contacts referenced by the domain and compile an EPP XML
// string that removes each one.
for (DesignatedContact designatedContact : domain.getContacts()) {
@Nullable Contact contact = contacts.get(designatedContact.getContactKey());
if (contact == null) {
logger.atWarning().log(
"Domain '%s' referenced contact with repo ID '%s' that couldn't be" + " loaded.",
domain.getDomainName(), designatedContact.getContactKey().getKey());
continue;
}
sb.append(
String.format(
CONTACT_FMT,
Ascii.toLowerCase(designatedContact.getType().name()),
contact.getContactId()))
.append("\n");
}
String compiledXml =
updateDomainXml
.replace("%DOMAIN%", domain.getDomainName())
.replace("%CONTACTS%", sb.toString());
EppOutput output =
eppController.handleEppCommand(
new StatelessRequestSessionMetadata(
registryAdminClientId, ProtocolDefinition.getVisibleServiceExtensionUris()),
new PasswordOnlyTransportCredentials(),
EppRequestSource.BACKEND,
false,
true,
compiledXml.getBytes(US_ASCII));
if (output.isSuccess()) {
logger.atInfo().log(
"Successfully removed contacts from domain '%s'.", domain.getDomainName());
} else {
logger.atWarning().log(
"Failed removing contacts from domain '%s' with error %s.",
domain.getDomainName(), new String(marshalWithLenientRetry(output), US_ASCII));
}
return output.isSuccess();
}
}

View File

@@ -1,142 +0,0 @@
// Copyright 2021 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.batch;
import static google.registry.beam.BeamUtils.createJobName;
import static google.registry.request.RequestParameters.PARAM_DRY_RUN;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.api.services.dataflow.Dataflow;
import com.google.api.services.dataflow.model.LaunchFlexTemplateParameter;
import com.google.api.services.dataflow.model.LaunchFlexTemplateRequest;
import com.google.api.services.dataflow.model.LaunchFlexTemplateResponse;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.beam.wipeout.WipeOutContactHistoryPiiPipeline;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.contact.ContactHistory;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.RegistryEnvironment;
import jakarta.inject.Inject;
import java.io.IOException;
import java.util.Optional;
import org.joda.time.DateTime;
/**
* An action that launches {@link WipeOutContactHistoryPiiPipeline} to wipe out Personal
* Identifiable Information (PII) fields of {@link ContactHistory} entities.
*
* <p>{@link ContactHistory} entities should be retained in the database for only certain amount of
* time.
*/
@Action(
service = Action.Service.BACKEND,
path = WipeOutContactHistoryPiiAction.PATH,
auth = Auth.AUTH_ADMIN)
public class WipeOutContactHistoryPiiAction implements Runnable {
public static final String PATH = "/_dr/task/wipeOutContactHistoryPii";
public static final String PARAM_CUTOFF_TIME = "wipeoutTime";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String PIPELINE_NAME = "wipe_out_contact_history_pii_pipeline";
private final Clock clock;
private final boolean isDryRun;
private final Optional<DateTime> maybeCutoffTime;
private final int minMonthsBeforeWipeOut;
private final String stagingBucketUrl;
private final String projectId;
private final String jobRegion;
private final Dataflow dataflow;
private final Response response;
@Inject
public WipeOutContactHistoryPiiAction(
Clock clock,
@Parameter(PARAM_DRY_RUN) boolean isDryRun,
@Parameter(PARAM_CUTOFF_TIME) Optional<DateTime> maybeCutoffTime,
@Config("minMonthsBeforeWipeOut") int minMonthsBeforeWipeOut,
@Config("beamStagingBucketUrl") String stagingBucketUrl,
@Config("projectId") String projectId,
@Config("defaultJobRegion") String jobRegion,
Dataflow dataflow,
Response response) {
this.clock = clock;
this.isDryRun = isDryRun;
this.maybeCutoffTime = maybeCutoffTime;
this.minMonthsBeforeWipeOut = minMonthsBeforeWipeOut;
this.stagingBucketUrl = stagingBucketUrl;
this.projectId = projectId;
this.jobRegion = jobRegion;
this.dataflow = dataflow;
this.response = response;
}
@Override
public void run() {
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
DateTime cutoffTime =
maybeCutoffTime.orElse(clock.nowUtc().minusMonths(minMonthsBeforeWipeOut));
LaunchFlexTemplateParameter launchParameter =
new LaunchFlexTemplateParameter()
.setJobName(
createJobName(
String.format(
"contact-history-pii-wipeout-%s",
cutoffTime.toString("yyyy-MM-dd't'HH-mm-ss'z'")),
clock))
.setContainerSpecGcsPath(
String.format("%s/%s_metadata.json", stagingBucketUrl, PIPELINE_NAME))
.setParameters(
ImmutableMap.of(
"registryEnvironment",
RegistryEnvironment.get().name(),
"cutoffTime",
cutoffTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"),
"isDryRun",
Boolean.toString(isDryRun)));
logger.atInfo().log(
"Launching Beam pipeline to wipe out all PII of contact history entities prior to %s%s.",
cutoffTime, " in dry run mode");
try {
LaunchFlexTemplateResponse launchResponse =
dataflow
.projects()
.locations()
.flexTemplates()
.launch(
projectId,
jobRegion,
new LaunchFlexTemplateRequest().setLaunchParameter(launchParameter))
.execute();
logger.atInfo().log("Got response: %s", launchResponse.getJob().toPrettyString());
response.setStatus(SC_OK);
response.setPayload(
String.format(
"Launched contact history PII wipeout pipeline: %s",
launchResponse.getJob().getId()));
} catch (IOException e) {
logger.atWarning().withCause(e).log("Pipeline Launch failed");
response.setStatus(SC_INTERNAL_SERVER_ERROR);
response.setPayload(String.format("Pipeline launch failed: %s", e.getMessage()));
}
}
}

View File

@@ -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>
*
@@ -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) {

View File

@@ -1,166 +0,0 @@
// Copyright 2023 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.beam.wipeout;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.apache.beam.sdk.values.TypeDescriptors.voids;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
import google.registry.beam.common.RegistryJpaIO;
import google.registry.model.contact.ContactHistory;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.VKey;
import java.io.Serializable;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.PipelineResult;
import org.apache.beam.sdk.coders.KvCoder;
import org.apache.beam.sdk.coders.StringUtf8Coder;
import org.apache.beam.sdk.coders.VarLongCoder;
import org.apache.beam.sdk.metrics.Counter;
import org.apache.beam.sdk.metrics.Metrics;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.MapElements;
import org.apache.beam.sdk.transforms.join.CoGroupByKey;
import org.apache.beam.sdk.transforms.join.KeyedPCollectionTuple;
import org.apache.beam.sdk.values.KV;
import org.apache.beam.sdk.values.PCollection;
import org.apache.beam.sdk.values.TupleTag;
import org.joda.time.DateTime;
/**
* Definition of a Dataflow Flex pipeline template, which finds out {@link ContactHistory} entries
* that are older than a given age (excluding the most recent one, even if it falls with the range)
* and wipe out PII information in them.
*
* <p>To stage this template locally, run {@code ./nom_build :core:sBP --environment=alpha \
* --pipeline=wipeOutContactHistoryPii}.
*
* <p>Then, you can run the staged template via the API client library, gCloud or a raw REST call.
*/
public class WipeOutContactHistoryPiiPipeline implements Serializable {
private static final long serialVersionUID = -4111052675715913820L;
private static final TupleTag<Long> REVISIONS_TO_WIPE = new TupleTag<>();
private static final TupleTag<Long> MOST_RECENT_REVISION = new TupleTag<>();
private final DateTime cutoffTime;
private final boolean dryRun;
private final Counter contactsInScope =
Metrics.counter("WipeOutContactHistoryPii", "contacts in scope");
private final Counter historiesToWipe =
Metrics.counter("WipeOutContactHistoryPii", "contact histories to wipe PII from");
private final Counter historiesWiped =
Metrics.counter("WipeOutContactHistoryPii", "contact histories actually updated");
WipeOutContactHistoryPiiPipeline(WipeOutContactHistoryPiiPipelineOptions options) {
dryRun = options.getIsDryRun();
cutoffTime = DateTime.parse(options.getCutoffTime());
}
void setup(Pipeline pipeline) {
KeyedPCollectionTuple.of(REVISIONS_TO_WIPE, getHistoryEntriesToWipe(pipeline))
.and(MOST_RECENT_REVISION, getMostRecentHistoryEntries(pipeline))
.apply("Group by contact", CoGroupByKey.create())
.apply(
"Wipe out PII",
MapElements.into(voids())
.via(
kv -> {
String repoId = kv.getKey();
long mostRecentRevision = kv.getValue().getOnly(MOST_RECENT_REVISION);
ImmutableList<Long> revisionsToWipe =
Streams.stream(kv.getValue().getAll(REVISIONS_TO_WIPE))
.filter(e -> e != mostRecentRevision)
.collect(toImmutableList());
if (revisionsToWipe.isEmpty()) {
return null;
}
contactsInScope.inc();
tm().transact(
() -> {
for (long revisionId : revisionsToWipe) {
historiesToWipe.inc();
ContactHistory history =
tm().loadByKey(
VKey.create(
ContactHistory.class,
new HistoryEntryId(repoId, revisionId)));
// In the unlikely case where multiple pipelines run at the
// same time, or where the runner decides to rerun a particular
// transform, we might have a history entry that has already been
// wiped at this point. There's no need to wipe it again.
if (!dryRun
&& history.getContactBase().isPresent()
&& history.getContactBase().get().getEmailAddress() != null) {
historiesWiped.inc();
tm().update(history.asBuilder().wipeOutPii().build());
}
}
});
return null;
}));
}
PCollection<KV<String, Long>> getHistoryEntriesToWipe(Pipeline pipeline) {
return pipeline.apply(
"Find contact histories to wipee",
// Email is one of the required fields in EPP, meaning it's initially not null when it
// is set by EPP flows (even though it is nullalbe in the SQL schema). Therefore,
// checking if it's null is one way to avoid processing contact history entities that
// have been processed previously. Refer to RFC 5733 for more information.
RegistryJpaIO.read(
"SELECT repoId, revisionId FROM ContactHistory WHERE resource.email IS NOT NULL"
+ " AND modificationTime < :cutoffTime",
ImmutableMap.of("cutoffTime", cutoffTime),
Object[].class,
row -> KV.of((String) row[0], (long) row[1]))
.withCoder(KvCoder.of(StringUtf8Coder.of(), VarLongCoder.of())));
}
PCollection<KV<String, Long>> getMostRecentHistoryEntries(Pipeline pipeline) {
return pipeline.apply(
"Find the most recent historiy entry for each contact",
RegistryJpaIO.read(
"SELECT repoId, revisionId FROM ContactHistory"
+ " WHERE (repoId, modificationTime) IN"
+ " (SELECT repoId, MAX(modificationTime) FROM ContactHistory GROUP BY repoId)",
ImmutableMap.of(),
Object[].class,
row -> KV.of((String) row[0], (long) row[1]))
.withCoder(KvCoder.of(StringUtf8Coder.of(), VarLongCoder.of())));
}
PipelineResult run(Pipeline pipeline) {
setup(pipeline);
return pipeline.run();
}
public static void main(String[] args) {
PipelineOptionsFactory.register(WipeOutContactHistoryPiiPipelineOptions.class);
WipeOutContactHistoryPiiPipelineOptions options =
PipelineOptionsFactory.fromArgs(args)
.withValidation()
.as(WipeOutContactHistoryPiiPipelineOptions.class);
// Repeatable read should be more than enough since we are dealing with old history entries that
// are otherwise immutable.
options.setIsolationOverride(TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ);
Pipeline pipeline = Pipeline.create(options);
new WipeOutContactHistoryPiiPipeline(options).run(pipeline);
}
}

View File

@@ -1,37 +0,0 @@
// Copyright 2023 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.beam.wipeout;
import google.registry.beam.common.RegistryPipelineOptions;
import org.apache.beam.sdk.options.Default;
import org.apache.beam.sdk.options.Description;
public interface WipeOutContactHistoryPiiPipelineOptions extends RegistryPipelineOptions {
@Description(
"A contact history entry with a history modification time before this time will have its PII"
+ " wiped, unless it is the most entry for the contact.")
String getCutoffTime();
void setCutoffTime(String value);
@Description(
"If true, the wiped out billing events will not be saved but the pipeline metrics counter"
+ " will still be updated.")
@Default.Boolean(false)
boolean getIsDryRun();
void setIsDryRun(boolean value);
}

View File

@@ -1264,12 +1264,6 @@ public final class RegistryConfig {
return ImmutableSet.copyOf(config.sslCertificateValidation.allowedEcdsaCurves);
}
@Provides
@Config("minMonthsBeforeWipeOut")
public static int provideMinMonthsBeforeWipeOut(RegistryConfigSettings config) {
return config.contactHistory.minMonthsBeforeWipeOut;
}
@Provides
@Config("jdbcBatchSize")
public static int provideHibernateJdbcBatchSize(RegistryConfigSettings config) {
@@ -1463,9 +1457,9 @@ public final class RegistryConfig {
}
@Provides
@Config("mosapiTldThreadCnt")
@Config("mosapiTldThreadCount")
public static int provideMosapiTldThreads(RegistryConfigSettings config) {
return config.mosapi.tldThreadCnt;
return config.mosapi.tldThreadCount;
}
private static String formatComments(String text) {

View File

@@ -39,7 +39,6 @@ public class RegistryConfigSettings {
public Beam beam;
public RegistryTool registryTool;
public SslCertificateValidation sslCertificateValidation;
public ContactHistory contactHistory;
public DnsUpdate dnsUpdate;
public BulkPricingPackageMonitoring bulkPricingPackageMonitoring;
public Bsa bsa;
@@ -223,11 +222,6 @@ public class RegistryConfigSettings {
public String expirationWarningEmailSubjectText;
}
/** Configuration for contact history. */
public static class ContactHistory {
public int minMonthsBeforeWipeOut;
}
/** Configuration for dns update. */
public static class DnsUpdate {
public String dnsUpdateFailEmailSubjectText;
@@ -272,6 +266,6 @@ public class RegistryConfigSettings {
public String entityType;
public List<String> tlds;
public List<String> services;
public int tldThreadCnt;
public int tldThreadCount;
}
}

View File

@@ -450,11 +450,6 @@ registryTool:
# OAuth client secret used by the tool.
clientSecret: YOUR_CLIENT_SECRET
# Configuration options for handling contact history.
contactHistory:
# The number of months that a ContactHistory entity should be stored in the database.
minMonthsBeforeWipeOut: 18
# Configuration options relevant to the DNS update functionality.
dnsUpdate:
dnsUpdateFailRegistryName: Example name
@@ -645,5 +640,5 @@ mosapi:
# 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
tldThreadCount: 4

View File

@@ -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>

View File

@@ -22,7 +22,6 @@ import com.google.common.base.Strings;
import dagger.Module;
import dagger.Provides;
import google.registry.flows.picker.FlowPicker;
import google.registry.model.contact.ContactHistory;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo;
@@ -267,23 +266,6 @@ public class FlowModule {
return builder;
}
/**
* Provides a partially filled in {@link ContactHistory.Builder}
*
* <p>This is not marked with {@link FlowScope} so that each retry gets a fresh one. Otherwise,
* the fact that the builder is one-use would cause NPEs.
*/
@Provides
static ContactHistory.Builder provideContactHistoryBuilder(
Trid trid,
@InputXml byte[] inputXmlBytes,
@Superuser boolean isSuperuser,
@RegistrarId String registrarId,
EppInput eppInput) {
return makeHistoryEntryBuilder(
new ContactHistory.Builder(), trid, inputXmlBytes, isSuperuser, registrarId, eppInput);
}
/**
* Provides a partially filled in {@link HostHistory.Builder}
*

View File

@@ -16,7 +16,6 @@ package google.registry.flows;
import static com.google.common.collect.Sets.intersection;
import static google.registry.model.EppResourceUtils.isLinked;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
@@ -37,7 +36,6 @@ import google.registry.model.EppResource;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.Period;
@@ -124,14 +122,6 @@ public final class ResourceFlowUtils {
}
}
/** Check that the given AuthInfo is either missing or else is valid for the given resource. */
public static void verifyOptionalAuthInfo(Optional<AuthInfo> authInfo, Contact contact)
throws EppException {
if (authInfo.isPresent()) {
verifyAuthInfo(authInfo.get(), contact);
}
}
/** Check that the given AuthInfo is either missing or else is valid for the given resource. */
public static void verifyOptionalAuthInfo(Optional<AuthInfo> authInfo, Domain domain)
throws EppException {
@@ -142,37 +132,14 @@ public final class ResourceFlowUtils {
/** Check that the given {@link AuthInfo} is valid for the given domain. */
public static void verifyAuthInfo(AuthInfo authInfo, Domain domain) throws EppException {
final String authRepoId = authInfo.getPw().getRepoId();
String authPassword = authInfo.getPw().getValue();
if (authRepoId == null) {
// If no roid is specified, check the password against the domain's password.
String domainPassword = domain.getAuthInfo().getPw().getValue();
if (!domainPassword.equals(authPassword)) {
throw new BadAuthInfoForResourceException();
}
return;
}
// The roid should match one of the contacts.
Optional<VKey<Contact>> foundContact =
domain.getReferencedContacts().stream()
.filter(key -> key.getKey().equals(authRepoId))
.findFirst();
if (foundContact.isEmpty()) {
String authRepoId = authInfo.getPw().getRepoId();
// Previously one could auth against a contact, but we no longer hold any contact info
if (authRepoId != null) {
throw new BadAuthInfoForResourceException();
}
// Check the authInfo against the contact.
verifyAuthInfo(authInfo, tm().loadByKey(foundContact.get()));
}
/** Check that the given {@link AuthInfo} is valid for the given contact. */
public static void verifyAuthInfo(AuthInfo authInfo, Contact contact) throws EppException {
String authRepoId = authInfo.getPw().getRepoId();
String authPassword = authInfo.getPw().getValue();
String contactPassword = contact.getAuthInfo().getPw().getValue();
if (!contactPassword.equals(authPassword)
// It's unnecessary to specify a repoId on a contact auth info, but if it's there validate
// it. The usual case of this is validating a domain's auth using this method.
|| (authRepoId != null && !authRepoId.equals(contact.getRepoId()))) {
String domainPassword = domain.getAuthInfo().getPw().getValue();
if (!domainPassword.equals(authPassword)) {
throw new BadAuthInfoForResourceException();
}
}
@@ -194,13 +161,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 +247,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) {

View File

@@ -167,7 +167,6 @@ import org.joda.time.Duration;
* @error {@link DomainFlowUtils.DomainLabelBlockedByBsaException}
* @error {@link DomainFlowUtils.DomainLabelTooLongException}
* @error {@link DomainFlowUtils.DomainReservedException}
* @error {@link DomainFlowUtils.DuplicateContactForRoleException}
* @error {@link DomainFlowUtils.EmptyDomainNamePartException}
* @error {@link DomainFlowUtils.ExceedsMaxRegistrationYearsException}
* @error {@link DomainFlowUtils.ExpiredClaimException}
@@ -188,7 +187,6 @@ import org.joda.time.Duration;
* @error {@link DomainFlowUtils.MaxSigLifeNotSupportedException}
* @error {@link DomainFlowUtils.MissingBillingAccountMapException}
* @error {@link DomainFlowUtils.MissingClaimsNoticeException}
* @error {@link DomainFlowUtils.MissingContactTypeException}
* @error {@link DomainFlowUtils.NameserversNotAllowedForTldException}
* @error {@link DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException}
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
@@ -221,7 +219,8 @@ public final class DomainCreateFlow implements MutatingFlow {
@Inject DomainPricingLogic pricingLogic;
@Inject DomainDeletionTimeCache domainDeletionTimeCache;
@Inject DomainCreateFlow() {}
@Inject
DomainCreateFlow() {}
@Override
public EppResponse run() throws EppException {
@@ -378,12 +377,10 @@ public final class DomainCreateFlow implements MutatingFlow {
.setLaunchNotice(hasClaimsNotice ? launchCreate.get().getNotice() : null)
.setSmdId(signedMarkId)
.setDsData(secDnsCreate.map(SecDnsCreateExtension::getDsData).orElse(null))
.setRegistrant(command.getRegistrant())
.setAuthInfo(command.getAuthInfo())
.setDomainName(targetId)
.setNameservers(command.getNameservers().stream().collect(toImmutableSet()))
.setStatusValues(statuses)
.setContacts(command.getContacts())
.addGracePeriod(
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, repoId, createBillingEvent))
.setLordnPhase(

View File

@@ -157,7 +157,8 @@ public final class DomainFlowTmchUtils {
}
/** The provided mark does not match the desired domain label. */
static class NoMarksFoundMatchingDomainException extends RequiredParameterMissingException {
public static class NoMarksFoundMatchingDomainException
extends RequiredParameterMissingException {
public NoMarksFoundMatchingDomainException() {
super("The provided mark does not match the desired domain label");
}

View File

@@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.intersection;
import static com.google.common.collect.Sets.union;
@@ -45,10 +44,8 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.isAtOrAfter;
import static google.registry.util.DateTimeUtils.leapSafeAddYears;
import static google.registry.util.DomainNameUtils.ACE_PREFIX;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.joining;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
@@ -57,9 +54,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.net.InternetDomainName;
@@ -81,14 +75,12 @@ import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingRecurrence;
import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand.Create;
import google.registry.model.domain.DomainCommand.CreateOrUpdate;
import google.registry.model.domain.DomainCommand.InvalidReferencesException;
import google.registry.model.domain.DomainCommand.Update;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.ForeignKeyedDesignatedContact;
import google.registry.model.domain.Period;
import google.registry.model.domain.Period.Unit;
import google.registry.model.domain.fee.BaseFee;
@@ -133,10 +125,8 @@ import google.registry.tldconfig.idn.IdnLabelValidator;
import google.registry.tools.DigestType;
import google.registry.util.Idn;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
@@ -405,22 +395,11 @@ public class DomainFlowUtils {
return period;
}
/** Verify that no linked resources have disallowed statuses. */
static void verifyNotInPendingDelete(
Set<DesignatedContact> contacts,
Optional<VKey<Contact>> registrant,
Set<VKey<Host>> nameservers)
throws EppException {
ImmutableList.Builder<VKey<? extends EppResource>> keysToLoad = new ImmutableList.Builder<>();
contacts.stream().map(DesignatedContact::getContactKey).forEach(keysToLoad::add);
registrant.ifPresent(keysToLoad::add);
keysToLoad.addAll(nameservers);
verifyNotInPendingDelete(EppResource.loadByCacheIfEnabled(keysToLoad.build()).values());
}
private static void verifyNotInPendingDelete(Iterable<EppResource> resources)
throws EppException {
for (EppResource resource : resources) {
/** Verify that no linked nameservers have disallowed statuses. */
static void verifyNotInPendingDelete(ImmutableSet<VKey<Host>> nameservers)
throws StatusProhibitsOperationException {
for (EppResource resource :
EppResource.loadByCacheIfEnabled(ImmutableSet.copyOf(nameservers)).values()) {
if (resource.getStatusValues().contains(StatusValue.PENDING_DELETE)) {
throw new LinkedResourceInPendingDeleteProhibitsOperationException(
resource.getForeignKey());
@@ -428,15 +407,6 @@ public class DomainFlowUtils {
}
}
static void validateContactsHaveTypes(Set<DesignatedContact> contacts)
throws ParameterValuePolicyErrorException {
for (DesignatedContact contact : contacts) {
if (contact.getType() == null) {
throw new MissingContactTypeException();
}
}
}
static void validateNameserversCountForTld(String tld, InternetDomainName domainName, int count)
throws EppException {
// For TLDs with a nameserver allow list, all domains must have at least 1 nameserver.
@@ -451,36 +421,22 @@ public class DomainFlowUtils {
}
}
static void validateNoDuplicateContacts(Set<DesignatedContact> contacts)
/** Enforces absence of contact data on creation as part of the Minimum Dataset requirements. */
static void enforceContactAbsencesOnCreate(Create create)
throws ParameterValuePolicyErrorException {
ImmutableMultimap<Type, VKey<Contact>> contactsByType =
contacts.stream()
.collect(
toImmutableSetMultimap(
DesignatedContact::getType, DesignatedContact::getContactKey));
// If any contact type has multiple contacts:
if (contactsByType.asMap().values().stream().anyMatch(v -> v.size() > 1)) {
// Find the duplicates.
Map<Type, Collection<VKey<Contact>>> dupeKeysMap =
Maps.filterEntries(contactsByType.asMap(), e -> e.getValue().size() > 1);
ImmutableList<VKey<Contact>> dupeKeys =
dupeKeysMap.values().stream().flatMap(Collection::stream).collect(toImmutableList());
// Load the duplicates in one batch.
Map<VKey<? extends Contact>, Contact> dupeContacts = tm().loadByKeys(dupeKeys);
ImmutableMultimap.Builder<Type, VKey<Contact>> typesMap = new ImmutableMultimap.Builder<>();
dupeKeysMap.forEach(typesMap::putAll);
// Create an error message showing the type and contact IDs of the duplicates.
throw new DuplicateContactForRoleException(
Multimaps.transformValues(typesMap.build(), key -> dupeContacts.get(key).getContactId()));
}
enforceContactAbsences(create.getRegistrant(), create.getContacts());
}
/**
* Enforces the presence/absence of contact data on domain creates depending on the minimum data
* set migration schedule.
*/
static void validateCreateContactData(
/** Enforces absence of contact data on update as part of the Minimum Dataset requirements. */
static void enforceContactAbsencesOnUpdate(Update update)
throws ParameterValuePolicyErrorException {
Set<DesignatedContact> allDesignatedContacts =
Sets.union(update.getInnerAdd().getContacts(), update.getInnerRemove().getContacts());
enforceContactAbsences(update.getInnerChange().getRegistrant(), allDesignatedContacts);
}
/** Enforces the absence of contact data as part of the Minimum Dataset requirements. */
static void enforceContactAbsences(
Optional<VKey<Contact>> registrant, Set<DesignatedContact> contacts)
throws ParameterValuePolicyErrorException {
if (registrant.isPresent()) {
@@ -491,25 +447,6 @@ public class DomainFlowUtils {
}
}
/**
* Enforces the presence/absence of contact data on domain updates depending on the minimum data
* set migration schedule.
*/
static void validateUpdateContactData(
Optional<VKey<Contact>> existingRegistrant,
Optional<VKey<Contact>> newRegistrant,
Set<DesignatedContact> existingContacts,
Set<DesignatedContact> newContacts)
throws ParameterValuePolicyErrorException {
// Throw if the update specifies a new registrant that is different from the existing one.
if (newRegistrant.isPresent() && !newRegistrant.equals(existingRegistrant)) {
throw new RegistrantProhibitedException();
}
// Throw if the update specifies any new contacts that weren't already present on the domain.
if (!Sets.difference(newContacts, existingContacts).isEmpty()) {
throw new ContactsProhibitedException();
}
}
static void validateNameserversAllowedOnTld(String tld, Set<String> fullyQualifiedHostNames)
throws EppException {
@@ -976,23 +913,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));
}
@@ -1034,12 +969,9 @@ public class DomainFlowUtils {
/** Validate the contacts and nameservers specified in a domain create command. */
static void validateCreateCommandContactsAndNameservers(
Create command, Tld tld, InternetDomainName domainName) throws EppException {
verifyNotInPendingDelete(
command.getContacts(), command.getRegistrant(), command.getNameservers());
validateContactsHaveTypes(command.getContacts());
verifyNotInPendingDelete(command.getNameservers());
String tldStr = tld.getTldStr();
validateNoDuplicateContacts(command.getContacts());
validateCreateContactData(command.getRegistrant(), command.getContacts());
enforceContactAbsencesOnCreate(command);
ImmutableSet<String> hostNames = command.getNameserverHostNames();
validateNameserversCountForTld(tldStr, domainName, hostNames.size());
validateNameserversAllowedOnTld(tldStr, hostNames);
@@ -1145,17 +1077,6 @@ public class DomainFlowUtils {
.build();
}
static ImmutableSet<ForeignKeyedDesignatedContact> loadForeignKeyedDesignatedContacts(
ImmutableSet<DesignatedContact> contacts) {
ImmutableSet.Builder<ForeignKeyedDesignatedContact> builder = new ImmutableSet.Builder<>();
for (DesignatedContact contact : contacts) {
builder.add(
ForeignKeyedDesignatedContact.create(
contact.getType(), tm().loadByKey(contact.getContactKey()).getContactId()));
}
return builder.build();
}
/**
* Returns a set of DomainTransactionRecords which negate the most recent HistoryEntry's records.
*
@@ -1295,32 +1216,6 @@ public class DomainFlowUtils {
}
}
/** Missing type attribute for contact. */
static class MissingContactTypeException extends ParameterValuePolicyErrorException {
public MissingContactTypeException() {
super("Missing type attribute for contact");
}
}
/** More than one contact for a given role is not allowed. */
static class DuplicateContactForRoleException extends ParameterValuePolicyErrorException {
public DuplicateContactForRoleException(Multimap<Type, String> dupeContactsByType) {
super(
String.format(
"More than one contact for a given role is not allowed: %s",
dupeContactsByType.asMap().entrySet().stream()
.sorted(comparing(e -> e.getKey().name()))
.map(
e ->
String.format(
"role [%s] has contacts [%s]",
Ascii.toLowerCase(e.getKey().name()),
e.getValue().stream().sorted().collect(joining(", "))))
.collect(joining(", "))));
}
}
/** Declared launch extension phase does not match the current registry phase. */
static class LaunchPhaseMismatchException extends ParameterValuePolicyErrorException {
public LaunchPhaseMismatchException() {

View File

@@ -19,7 +19,6 @@ import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
import static google.registry.flows.domain.DomainFlowUtils.addSecDnsExtensionIfPresent;
import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
import static google.registry.flows.domain.DomainFlowUtils.loadForeignKeyedDesignatedContacts;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableList;
@@ -126,15 +125,11 @@ public final class DomainInfoFlow implements MutatingFlow {
.setLastEppUpdateTime(domain.getLastEppUpdateTime())
.setRegistrationExpirationTime(domain.getRegistrationExpirationTime())
.setLastTransferTime(domain.getLastTransferTime());
domain
.getRegistrant()
.ifPresent(r -> infoBuilder.setRegistrant(tm().loadByKey(r).getContactId()));
// If authInfo is non-null, then the caller is authorized to see the full information since we
// will have already verified the authInfo is valid.
if (registrarId.equals(domain.getCurrentSponsorRegistrarId()) || authInfo.isPresent()) {
infoBuilder
.setContacts(loadForeignKeyedDesignatedContacts(domain.getContacts()))
.setSubordinateHosts(
hostsRequest.requestSubordinate() ? domain.getSubordinateHosts() : null)
.setCreationRegistrarId(domain.getCreationRegistrarId())

View File

@@ -21,22 +21,20 @@ 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;
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences;
import static google.registry.flows.domain.DomainFlowUtils.enforceContactAbsencesOnUpdate;
import static google.registry.flows.domain.DomainFlowUtils.updateDsData;
import static google.registry.flows.domain.DomainFlowUtils.validateContactsHaveTypes;
import static google.registry.flows.domain.DomainFlowUtils.validateDsData;
import static google.registry.flows.domain.DomainFlowUtils.validateFeesAckedIfPresent;
import static google.registry.flows.domain.DomainFlowUtils.validateNameserversAllowedOnTld;
import static google.registry.flows.domain.DomainFlowUtils.validateNameserversCountForTld;
import static google.registry.flows.domain.DomainFlowUtils.validateNoDuplicateContacts;
import static google.registry.flows.domain.DomainFlowUtils.validateUpdateContactData;
import static google.registry.flows.domain.DomainFlowUtils.verifyClientUpdateNotProhibited;
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_UPDATE;
@@ -64,8 +62,6 @@ import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedExceptio
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingEvent;
import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand.Update;
import google.registry.model.domain.DomainCommand.Update.AddRemove;
@@ -75,6 +71,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;
@@ -86,7 +84,6 @@ import google.registry.model.poll.PendingActionNotificationResponse.DomainPendin
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Tld;
import google.registry.persistence.VKey;
import jakarta.inject.Inject;
import java.util.Objects;
import java.util.Optional;
@@ -95,8 +92,8 @@ import org.joda.time.DateTime;
/**
* An EPP flow that updates a domain.
*
* <p>Updates can change contacts, nameservers and delegation signer data of a domain. Updates
* cannot change the domain's name.
* <p>Updates can change nameservers and delegation signer data of a domain. Updates cannot change
* the domain's name.
*
* <p>Some status values (those of the form "serverSomethingProhibited") can only be applied by the
* superuser. As such, adding or removing these statuses incurs a billing event. There will be only
@@ -111,7 +108,6 @@ import org.joda.time.DateTime;
* @error {@link google.registry.flows.exceptions.OnlyToolCanPassMetadataException}
* @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException}
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
* @error {@link DomainFlowUtils.DuplicateContactForRoleException}
* @error {@link DomainFlowUtils.EmptySecDnsUpdateException}
* @error {@link DomainFlowUtils.FeesMismatchException}
* @error {@link DomainFlowUtils.FeesRequiredForNonFreeOperationException}
@@ -119,7 +115,6 @@ import org.joda.time.DateTime;
* @error {@link DomainFlowUtils.LinkedResourcesDoNotExistException}
* @error {@link DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException}
* @error {@link DomainFlowUtils.MaxSigLifeChangeNotSupportedException}
* @error {@link DomainFlowUtils.MissingContactTypeException}
* @error {@link DomainFlowUtils.NameserversNotAllowedForTldException}
* @error {@link NameserversNotSpecifiedForTldWithNameserverAllowListException}
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
@@ -156,7 +151,9 @@ public final class DomainUpdateFlow implements MutatingFlow {
@Inject EppResponse.Builder responseBuilder;
@Inject DomainUpdateFlowCustomLogic flowCustomLogic;
@Inject DomainPricingLogic pricingLogic;
@Inject DomainUpdateFlow() {}
@Inject
DomainUpdateFlow() {}
@Override
public EppResponse run() throws EppException {
@@ -177,7 +174,7 @@ public final class DomainUpdateFlow implements MutatingFlow {
Domain newDomain = performUpdate(command, existingDomain, now);
DomainHistory domainHistory =
historyBuilder.setType(DOMAIN_UPDATE).setDomain(newDomain).build();
validateNewState(existingDomain, newDomain);
validateNewState(newDomain);
if (requiresDnsUpdate(existingDomain, newDomain)) {
requestDomainDnsRefresh(targetId);
}
@@ -233,30 +230,26 @@ public final class DomainUpdateFlow implements MutatingFlow {
eppInput.getSingleExtension(FeeUpdateCommandExtension.class);
FeesAndCredits feesAndCredits = pricingLogic.getUpdatePrice(tld, targetId, now);
validateFeesAckedIfPresent(feeUpdate, feesAndCredits, false);
verifyNotInPendingDelete(
add.getContacts(),
command.getInnerChange().getRegistrant(),
add.getNameservers());
validateContactsHaveTypes(add.getContacts());
validateContactsHaveTypes(remove.getContacts());
verifyNotInPendingDelete(add.getNameservers());
validateNameserversAllowedOnTld(tldStr, add.getNameserverHostNames());
}
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);
// We have to verify no duplicate contacts _before_ constructing the domain because it is
// illegal to construct a domain with duplicate contacts.
Sets.SetView<DesignatedContact> newContacts =
union(Sets.difference(domain.getContacts(), remove.getContacts()), add.getContacts());
validateNoDuplicateContacts(newContacts);
verifyAddsAndRemoves(domain.getNameservers(), add.getNameservers(), remove.getNameservers());
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();
enforceContactAbsencesOnUpdate(command);
Domain.Builder domainBuilder =
domain
@@ -276,9 +269,6 @@ public final class DomainUpdateFlow implements MutatingFlow {
.setLastEppUpdateRegistrarId(registrarId)
.addStatusValues(add.getStatusValues())
.removeStatusValues(remove.getStatusValues())
.removeContacts(remove.getContacts())
.addContacts(add.getContacts())
.setRegistrant(determineUpdatedRegistrant(change, domain))
.setAuthInfo(Optional.ofNullable(change.getAuthInfo()).orElse(domain.getAuthInfo()));
if (!add.getNameservers().isEmpty()) {
@@ -300,15 +290,6 @@ public final class DomainUpdateFlow implements MutatingFlow {
return domainBuilder.build();
}
private Optional<VKey<Contact>> determineUpdatedRegistrant(Change change, Domain domain) {
// During or after the minimum dataset transition, allow registrant to be removed.
if (change.getRegistrantContactId().isPresent()
&& change.getRegistrantContactId().get().isEmpty()) {
return Optional.empty();
}
return change.getRegistrant().or(domain::getRegistrant);
}
/**
* Checks whether the new state of the domain is valid.
*
@@ -316,13 +297,7 @@ public final class DomainUpdateFlow implements MutatingFlow {
* compliant with the additions or amendments, otherwise existing data can become invalid and
* cause Domain update failure.
*/
private static void validateNewState(Domain existingDomain, Domain newDomain)
throws EppException {
validateUpdateContactData(
existingDomain.getRegistrant(),
newDomain.getRegistrant(),
existingDomain.getContacts(),
newDomain.getContacts());
private static void validateNewState(Domain newDomain) throws EppException {
validateDsData(newDomain.getDsData());
validateNameserversCountForTld(
newDomain.getTld(),
@@ -336,8 +311,8 @@ public final class DomainUpdateFlow implements MutatingFlow {
Optional<MetadataExtension> metadataExtension =
eppInput.getSingleExtension(MetadataExtension.class);
if (metadataExtension.isPresent() && metadataExtension.get().getRequestedByRegistrar()) {
for (StatusValue statusValue
: symmetricDifference(existingDomain.getStatusValues(), newDomain.getStatusValues())) {
for (StatusValue statusValue :
symmetricDifference(existingDomain.getStatusValues(), newDomain.getStatusValues())) {
if (statusValue.isChargedStatus()) {
// Only charge once.
return Optional.of(

View File

@@ -17,6 +17,7 @@ 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;
@@ -34,7 +35,6 @@ 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;
@@ -57,7 +57,7 @@ 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);
}

View File

@@ -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,10 @@ 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);

View File

@@ -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();
}
}

View File

@@ -76,7 +76,7 @@ public class FeeCheckCommandExtensionStdV1 extends ImmutableObject
}
/** Domains across multiple currencies cannot be checked simultaneously. */
static class MultipleCurrenciesCannotBeCheckedException
public static class MultipleCurrenciesCannotBeCheckedException
extends ParameterValuePolicyErrorException {
public MultipleCurrenciesCannotBeCheckedException() {
// The fee extension 1.0 only supports one currency shared across all results

View File

@@ -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", "effectiveDate", "notAfterDate"})
@XmlType(propOrder = {"period", "fee"})
public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
/** The command that was checked. */
@@ -53,14 +51,6 @@ public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
*/
List<Fee> fee;
/** 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;
/** Builder for {@link FeeCheckResponseExtensionItemCommandStdV1}. */
public static class Builder extends Buildable.Builder<FeeCheckResponseExtensionItemCommandStdV1> {
@@ -84,16 +74,6 @@ 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;

View File

@@ -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", "feeClass", "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;
@@ -88,7 +86,7 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
@Override
public Builder setDomainNameIfSupported(String name) {
getInstance().object = new DomainObjectSpec(name);
getInstance().objID = name;
return this;
}
@@ -97,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;
}
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -14,13 +14,16 @@
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.RegistryEnvironment;
import google.registry.xml.ValidationMode;
import google.registry.xml.XmlException;
import google.registry.xml.XmlTransformer;
@@ -31,7 +34,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 +57,24 @@ public class EppXmlTransformer {
"allocationToken-1.0.xsd",
"bulkToken.xsd");
// 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 INPUT_TRANSFORMER =
new XmlTransformer(SCHEMAS, EppInput.class);
new XmlTransformer(getSchemas(), EppInput.class);
private static final XmlTransformer OUTPUT_TRANSFORMER =
new XmlTransformer(SCHEMAS, EppOutput.class);
new XmlTransformer(getSchemas(), EppOutput.class);
@VisibleForTesting
public static ImmutableList<String> getSchemas() {
if (RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION)) {
return ALL_SCHEMAS.stream()
.filter(s -> !NON_PROD_SCHEMAS.contains(s))
.collect(toImmutableList());
}
return ALL_SCHEMAS;
}
public static void validateOutput(String xml) throws XmlException {
OUTPUT_TRANSFORMER.validate(xml);

View File

@@ -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;
@@ -48,30 +51,49 @@ public class ProtocolDefinition {
"urn:ietf:params:xml:ns:domain-1.0",
"urn:ietf:params:xml:ns:contact-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_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.ALL),
FEE_0_11(
FeeCheckCommandExtensionV11.class,
FeeCheckResponseExtensionV11.class,
ServiceExtensionVisibility.ALL),
FEE_0_12(
FeeCheckCommandExtensionV12.class,
FeeCheckResponseExtensionV12.class,
ServiceExtensionVisibility.ALL),
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 +108,19 @@ 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_NON_PRODUCTION ->
!RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION);
case NONE -> false;
};
}
}
/**
@@ -110,15 +137,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());
}
}

View File

@@ -27,7 +27,6 @@ import google.registry.model.UnsafeSerializable;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.IdAllocation;
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.domain.DomainRenewData;
@@ -272,12 +271,6 @@ public abstract class PollMessage extends ImmutableObject
return thisCastToDerived();
}
public B setContactHistoryId(HistoryEntryId historyId) {
getInstance().contactRepoId = historyId.getRepoId();
getInstance().contactHistoryRevisionId = historyId.getRevisionId();
return thisCastToDerived();
}
public B setHostHistoryId(HistoryEntryId historyId) {
getInstance().hostRepoId = historyId.getRepoId();
getInstance().hostHistoryRevisionId = historyId.getRevisionId();
@@ -290,9 +283,6 @@ public abstract class PollMessage extends ImmutableObject
if (history instanceof DomainHistory) {
return setDomainHistoryId(historyId);
}
if (history instanceof ContactHistory) {
return setContactHistoryId(historyId);
}
if (history instanceof HostHistory) {
return setHostHistoryId(historyId);
}

View File

@@ -23,8 +23,6 @@ import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.UnsafeSerializable;
import google.registry.model.annotations.IdAllocation;
import google.registry.model.contact.ContactBase;
import google.registry.model.contact.ContactHistory;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.eppcommon.Trid;
@@ -348,8 +346,6 @@ public abstract class HistoryEntry extends ImmutableObject
HistoryEntry.Builder<? extends HistoryEntry, ?> createBuilderForResource(E parent) {
if (parent instanceof DomainBase) {
return new DomainHistory.Builder().setDomain((DomainBase) parent);
} else if (parent instanceof ContactBase) {
return new ContactHistory.Builder().setContact((ContactBase) parent);
} else if (parent instanceof HostBase) {
return new HostHistory.Builder().setHost((HostBase) parent);
} else {

View File

@@ -25,8 +25,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
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;
@@ -46,8 +44,6 @@ public class HistoryEntryDao {
public static ImmutableMap<Class<? extends EppResource>, Class<? extends HistoryEntry>>
RESOURCE_TYPES_TO_HISTORY_TYPES =
ImmutableMap.of(
Contact.class,
ContactHistory.class,
Domain.class,
DomainHistory.class,
Host.class,
@@ -59,7 +55,6 @@ public class HistoryEntryDao {
return tm().transact(
() ->
new ImmutableList.Builder<HistoryEntry>()
.addAll(loadAllHistoryObjects(ContactHistory.class, afterTime, beforeTime))
.addAll(loadAllHistoryObjects(DomainHistory.class, afterTime, beforeTime))
.addAll(loadAllHistoryObjects(HostHistory.class, afterTime, beforeTime))
.build());
@@ -121,7 +116,6 @@ public class HistoryEntryDao {
return tm().reTransact(
() ->
Streams.concat(
loadHistoryObjectByRegistrarsInternal(ContactHistory.class, registrarIds),
loadHistoryObjectByRegistrarsInternal(DomainHistory.class, registrarIds),
loadHistoryObjectByRegistrarsInternal(HostHistory.class, registrarIds))
.sorted(Comparator.comparing(HistoryEntry::getModificationTime))

View File

@@ -24,11 +24,9 @@ import google.registry.batch.DeleteLoadTestDataAction;
import google.registry.batch.DeleteProberDataAction;
import google.registry.batch.ExpandBillingRecurrencesAction;
import google.registry.batch.RelockDomainAction;
import google.registry.batch.RemoveAllDomainContactsAction;
import google.registry.batch.ResaveAllEppResourcesPipelineAction;
import google.registry.batch.ResaveEntityAction;
import google.registry.batch.SendExpiringCertificateNotificationEmailAction;
import google.registry.batch.WipeOutContactHistoryPiiAction;
import google.registry.bsa.BsaDownloadAction;
import google.registry.bsa.BsaRefreshAction;
import google.registry.bsa.BsaValidateAction;
@@ -276,8 +274,6 @@ interface RequestComponent {
ReadinessProbeActionFrontend readinessProbeActionFrontend();
RemoveAllDomainContactsAction removeAllDomainContactsAction();
RdapAutnumAction rdapAutnumAction();
RdapDomainAction rdapDomainAction();
@@ -350,8 +346,6 @@ interface RequestComponent {
VerifyOteAction verifyOteAction();
WipeOutContactHistoryPiiAction wipeOutContactHistoryPiiAction();
@Subcomponent.Builder
abstract class Builder implements RequestComponentBuilder<RequestComponent> {
@Override

View File

@@ -0,0 +1,103 @@
// 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.monitoring.whitebox;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.monitoring.metrics.LabelDescriptor;
import com.google.monitoring.metrics.MetricRegistry;
import com.google.monitoring.metrics.MetricRegistryImpl;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
/** Exposes JVM metrics. */
@Singleton
class JvmMetrics {
private static final ImmutableSet<LabelDescriptor> TYPE_LABEL_SET =
ImmutableSet.of(LabelDescriptor.create("type", "Memory type (e.g., heap, non_heap)"));
private final MemoryMXBean memoryMxBean;
@Inject
JvmMetrics() {
this(ManagementFactory.getMemoryMXBean());
}
/** Constructor for testing. */
JvmMetrics(MemoryMXBean memoryMxBean) {
this.memoryMxBean = memoryMxBean;
}
/** Registers JVM gauges with the default registry. */
void register() {
MetricRegistry registry = MetricRegistryImpl.getDefault();
registry.newGauge(
"/jvm/memory/used",
"Current memory usage in bytes",
"bytes",
TYPE_LABEL_SET,
(Supplier<ImmutableMap<ImmutableList<String>, Long>>) this::getUsedMemory,
Long.class);
registry.newGauge(
"/jvm/memory/committed",
"Committed memory in bytes",
"bytes",
TYPE_LABEL_SET,
(Supplier<ImmutableMap<ImmutableList<String>, Long>>) this::getCommittedMemory,
Long.class);
registry.newGauge(
"/jvm/memory/max",
"Maximum memory in bytes",
"bytes",
TYPE_LABEL_SET,
(Supplier<ImmutableMap<ImmutableList<String>, Long>>) this::getMaxMemory,
Long.class);
}
ImmutableMap<ImmutableList<String>, Long> getUsedMemory() {
MemoryUsage heapUsage = memoryMxBean.getHeapMemoryUsage();
MemoryUsage nonHeapUsage = memoryMxBean.getNonHeapMemoryUsage();
return ImmutableMap.of(
ImmutableList.of("heap"), heapUsage.getUsed(),
ImmutableList.of("non_heap"), nonHeapUsage.getUsed());
}
ImmutableMap<ImmutableList<String>, Long> getCommittedMemory() {
MemoryUsage heapUsage = memoryMxBean.getHeapMemoryUsage();
MemoryUsage nonHeapUsage = memoryMxBean.getNonHeapMemoryUsage();
return ImmutableMap.of(
ImmutableList.of("heap"), heapUsage.getCommitted(),
ImmutableList.of("non_heap"), nonHeapUsage.getCommitted());
}
ImmutableMap<ImmutableList<String>, Long> getMaxMemory() {
MemoryUsage heapUsage = memoryMxBean.getHeapMemoryUsage();
MemoryUsage nonHeapUsage = memoryMxBean.getNonHeapMemoryUsage();
return ImmutableMap.of(
ImmutableList.of("heap"), heapUsage.getMax(),
ImmutableList.of("non_heap"), nonHeapUsage.getMax());
}
}

View File

@@ -32,7 +32,7 @@ import jakarta.inject.Named;
import jakarta.inject.Singleton;
import org.joda.time.Duration;
/** Dagger module for Google Stackdriver service connection objects. */
/** Dagger module for monitoring and Google Stackdriver service connection objects. */
@Module
public final class StackdriverModule {
@@ -77,7 +77,11 @@ public final class StackdriverModule {
@Provides
static MetricReporter provideMetricReporter(
MetricWriter metricWriter, @Config("metricsWriteInterval") Duration writeInterval) {
MetricWriter metricWriter,
@Config("metricsWriteInterval") Duration writeInterval,
JvmMetrics jvmMetrics) {
jvmMetrics.register();
return new MetricReporter(
metricWriter,
writeInterval.getStandardSeconds(),

View File

@@ -14,21 +14,346 @@
package google.registry.mosapi;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.services.monitoring.v3.Monitoring;
import com.google.api.services.monitoring.v3.model.CreateTimeSeriesRequest;
import com.google.api.services.monitoring.v3.model.LabelDescriptor;
import com.google.api.services.monitoring.v3.model.Metric;
import com.google.api.services.monitoring.v3.model.MetricDescriptor;
import com.google.api.services.monitoring.v3.model.MonitoredResource;
import com.google.api.services.monitoring.v3.model.Point;
import com.google.api.services.monitoring.v3.model.TimeInterval;
import com.google.api.services.monitoring.v3.model.TimeSeries;
import com.google.api.services.monitoring.v3.model.TypedValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.mosapi.MosApiModels.ServiceStatus;
import google.registry.mosapi.MosApiModels.TldServiceState;
import google.registry.request.lock.LockHandler;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.io.IOException;
import java.time.Instant;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
import org.joda.time.Duration;
/** Metrics Exporter for MoSAPI. */
public class MosApiMetrics {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject
public MosApiMetrics() {}
// Google Cloud Monitoring Limit: Max 200 TimeSeries per request
private static final int MAX_TIMESERIES_PER_REQUEST = 195;
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");
private static final int METRICS_ALREADY_EXIST = 409;
// Magic String Constants
private static final String METRIC_DOMAIN = "custom.googleapis.com/mosapi/";
private static final String PROJECT_RESOURCE_PREFIX = "projects/";
private static final String RESOURCE_TYPE_GLOBAL = "global";
private static final String LABEL_PROJECT_ID = "project_id";
private static final String LABEL_TLD = "tld";
private static final String LABEL_SERVICE_TYPE = "service_type";
// Lock Constants
private static final String LOCK_NAME = "MosApiMetricCreation";
private static final Duration LOCK_LEASE_TIME = Duration.standardHours(1);
// Metric Names
private static final String METRIC_TLD_STATUS = "tld_status";
private static final String METRIC_SERVICE_STATUS = "service_status";
private static final String METRIC_EMERGENCY_USAGE = "emergency_usage";
private static final String GAUGE_METRIC_KIND = "GAUGE";
// Metric Display Names & Descriptions
private static final String DISPLAY_NAME_TLD_STATUS =
"Health of TLDs. 1 = UP, 0 = DOWN, 2= DISABLED/NOT_MONITORED";
private static final String DESC_TLD_STATUS = "Overall Health of TLDs reported from ICANN";
private static final String DISPLAY_NAME_SERVICE_STATUS =
"Health of Services. 1 = UP, 0 = DOWN, 2= DISABLED/NOT_MONITORED";
private static final String DESC_SERVICE_STATUS =
"Overall Health of Services reported from ICANN";
private static final String DISPLAY_NAME_EMERGENCY_USAGE =
"Percentage of Emergency Threshold Consumed";
private static final String DESC_EMERGENCY_USAGE =
"Downtime threshold that if reached by any of the monitored Services may cause the TLDs"
+ " Services emergency transition to an interim Registry Operator";
// MoSAPI Status Constants
private static final String STATUS_UP_INCONCLUSIVE = "UP-INCONCLUSIVE";
private static final String STATUS_DOWN = "DOWN";
private static final String STATUS_DISABLED = "DISABLED";
private final Monitoring monitoringClient;
private final String projectId;
private final String projectName;
private final Clock clock;
private final MonitoredResource monitoredResource;
private final LockHandler lockHandler;
// Flag to ensure we only create descriptors once, lazily
@VisibleForTesting static final AtomicBoolean isDescriptorInitialized = new AtomicBoolean(false);
@Inject
public MosApiMetrics(
Monitoring monitoringClient,
@Config("projectId") String projectId,
Clock clock,
LockHandler lockHandler) {
this.monitoringClient = monitoringClient;
this.projectId = projectId;
this.clock = clock;
this.projectName = PROJECT_RESOURCE_PREFIX + projectId;
this.lockHandler = lockHandler;
this.monitoredResource =
new MonitoredResource()
.setType(RESOURCE_TYPE_GLOBAL)
.setLabels(ImmutableMap.of(LABEL_PROJECT_ID, projectId));
}
/** Accepts a list of states and processes them in a single async batch task. */
public void recordStates(ImmutableList<TldServiceState> states) {
// If this is the first time we are recording, ensure descriptors exist.
ensureMetricDescriptorsWithLock();
pushBatchMetrics(states);
}
/**
* Attempts to create metric descriptors using a distributed lock.
*
* <p>If the lock is acquired, this instance creates the descriptors and marks itself initialized.
* If the lock is busy, it implies another instance is handling it, so we skip and proceed.
*/
private void ensureMetricDescriptorsWithLock() {
lockHandler.executeWithLocks(
() -> {
if (!isDescriptorInitialized.get()) {
createCustomMetricDescriptors();
isDescriptorInitialized.set(true);
}
return null;
},
null,
LOCK_LEASE_TIME,
LOCK_NAME);
}
// Defines the custom metrics in Cloud Monitoring
private void createCustomMetricDescriptors() {
// 1. TLD Status Descriptor
createMetricDescriptor(
METRIC_TLD_STATUS,
DISPLAY_NAME_TLD_STATUS,
DESC_TLD_STATUS,
"INT64",
ImmutableList.of(LABEL_TLD));
// 2. Service Status Descriptor
createMetricDescriptor(
METRIC_SERVICE_STATUS,
DISPLAY_NAME_SERVICE_STATUS,
DESC_SERVICE_STATUS,
"INT64",
ImmutableList.of(LABEL_TLD, LABEL_SERVICE_TYPE));
// 3. Emergency Usage Descriptor
createMetricDescriptor(
METRIC_EMERGENCY_USAGE,
DISPLAY_NAME_EMERGENCY_USAGE,
DESC_EMERGENCY_USAGE,
"DOUBLE",
ImmutableList.of(LABEL_TLD, LABEL_SERVICE_TYPE));
logger.atInfo().log("Metric descriptors ensured for project %s", projectId);
}
private void createMetricDescriptor(
String metricTypeSuffix,
String displayName,
String description,
String valueType,
ImmutableList<String> labelKeys) {
ImmutableList<LabelDescriptor> labelDescriptors =
labelKeys.stream()
.map(
key ->
new LabelDescriptor()
.setKey(key)
.setValueType("STRING")
.setDescription(
key.equals(LABEL_TLD)
? "The TLD being monitored"
: "The type of service"))
.collect(toImmutableList());
MetricDescriptor descriptor =
new MetricDescriptor()
.setType(METRIC_DOMAIN + metricTypeSuffix)
.setMetricKind(GAUGE_METRIC_KIND)
.setValueType(valueType)
.setDisplayName(displayName)
.setDescription(description)
.setLabels(labelDescriptors);
try {
monitoringClient
.projects()
.metricDescriptors()
.create(this.projectName, descriptor)
.execute();
} catch (GoogleJsonResponseException e) {
if (e.getStatusCode() == METRICS_ALREADY_EXIST) {
// the metric already exists. This is expected.
logger.atFine().log("Metric descriptor %s already exists.", metricTypeSuffix);
} else {
logger.atWarning().withCause(e).log(
"Failed to create metric descriptor %s. Status: %d",
metricTypeSuffix, e.getStatusCode());
}
} catch (Exception e) {
logger.atWarning().withCause(e).log(
"Unexpected error creating metric descriptor %s.", metricTypeSuffix);
}
}
private void pushBatchMetrics(ImmutableList<TldServiceState> states) {
Instant now = Instant.ofEpochMilli(clock.nowUtc().getMillis());
TimeInterval interval = new TimeInterval().setEndTime(now.toString());
Stream<TimeSeries> allTimeSeriesStream =
states.stream().flatMap(state -> createMetricsForState(state, interval));
Iterator<List<TimeSeries>> batchIterator =
Iterators.partition(allTimeSeriesStream.iterator(), MAX_TIMESERIES_PER_REQUEST);
int successCount = 0;
int failureCount = 0;
// Iterate and count
while (batchIterator.hasNext()) {
List<TimeSeries> batch = batchIterator.next();
try {
CreateTimeSeriesRequest request = new CreateTimeSeriesRequest().setTimeSeries(batch);
monitoringClient.projects().timeSeries().create(this.projectName, request).execute();
successCount++;
} catch (IOException e) {
failureCount++;
// Log individual batch failures, so we have the stack trace for debugging
logger.atWarning().withCause(e).log(
"Failed to push batch of %d time series.", batch.size());
}
}
// 4. Log the final summary
if (failureCount > 0) {
logger.atWarning().log(
"Metric push finished with errors. Batches Succeeded: %d, Failed: %d",
successCount, failureCount);
} else {
logger.atInfo().log("Metric push finished successfully. Batches Succeeded: %d", successCount);
}
}
/** Generates all TimeSeries (TLD + Services) for a single state object. */
private Stream<TimeSeries> createMetricsForState(TldServiceState state, TimeInterval interval) {
// 1. TLD Status
Stream<TimeSeries> tldStream = Stream.of(createTldStatusTimeSeries(state, interval));
// 2. Service Metrics (if any)
Stream<TimeSeries> serviceStream =
state.serviceStatuses().entrySet().stream()
.flatMap(
entry ->
createServiceMetricsStream(
state.tld(), entry.getKey(), entry.getValue(), interval));
return Stream.concat(tldStream, serviceStream);
}
private Stream<TimeSeries> createServiceMetricsStream(
String tld, String serviceType, ServiceStatus statusObj, TimeInterval interval) {
ImmutableMap<String, String> labels =
ImmutableMap.of(LABEL_TLD, tld, LABEL_SERVICE_TYPE, serviceType);
return Stream.of(
createTimeSeries(
METRIC_SERVICE_STATUS, labels, parseServiceStatus(statusObj.status()), interval),
createTimeSeries(METRIC_EMERGENCY_USAGE, labels, statusObj.emergencyThreshold(), interval));
}
private TimeSeries createTldStatusTimeSeries(TldServiceState state, TimeInterval interval) {
return createTimeSeries(
METRIC_TLD_STATUS,
ImmutableMap.of(LABEL_TLD, state.tld()),
parseTldStatus(state.status()),
interval);
}
private TimeSeries createTimeSeries(
String suffix, ImmutableMap<String, String> labels, Number val, TimeInterval interval) {
Metric metric = new Metric().setType(METRIC_DOMAIN + suffix).setLabels(labels);
TypedValue tv = new TypedValue();
if (val instanceof Double) {
tv.setDoubleValue((Double) val);
} else {
tv.setInt64Value(val.longValue());
}
return new TimeSeries()
.setMetric(metric)
.setResource(this.monitoredResource)
.setPoints(ImmutableList.of(new Point().setInterval(interval).setValue(tv)));
}
/**
* Translates MoSAPI status to a numeric metric.
*
* <p>Mappings: 1 (UP) = Healthy; 0 (DOWN) = Critical failure; 2 (UP-INCONCLUSIVE) = Disabled/Not
* Monitored/In Maintenance.
*
* <p>A status of 2 indicates the SLA monitoring system is under maintenance. The TLD is
* considered "UP" by default, but individual service checks are disabled. This distinguishes
* maintenance windows from actual availability or outages.
*
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Spec Sec 5.1</a>
*/
private long parseTldStatus(String status) {
return switch (Ascii.toUpperCase(status)) {
case STATUS_DOWN -> 0;
case STATUS_UP_INCONCLUSIVE -> 2;
default -> 1; // status is up
};
}
/**
* Translates MoSAPI service status to a numeric metric.
*
* <p>Mappings: 1 (UP) = Healthy; 0 (DOWN) = Critical failure; 2 (DISABLED/UP-INCONCLUSIVE*) =
* Disabled/Not Monitored/In Maintenance.
*
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Spec Sec 5.1</a>
*/
private long parseServiceStatus(String status) {
String serviceStatus = Ascii.toUpperCase(status);
if (serviceStatus.startsWith(STATUS_UP_INCONCLUSIVE)) {
return 2;
}
return switch (serviceStatus) {
case STATUS_DOWN -> 0;
case STATUS_DISABLED -> 2;
default -> 1; // status is Up
};
}
}

View File

@@ -26,11 +26,9 @@ 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 {
@@ -135,14 +133,16 @@ public class MosApiStateService {
tldExecutor))
.collect(toImmutableList());
List<TldServiceState> allStates =
ImmutableList<TldServiceState> allStates =
futures.stream()
.map(CompletableFuture::join)
.filter(Objects::nonNull)
.collect(Collectors.toList());
.filter(this::isValidForMetrics)
.collect(toImmutableList());
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.");
@@ -151,4 +151,14 @@ public class MosApiStateService {
logger.atWarning().log("No successful TLD states fetched; skipping metrics push.");
}
}
private boolean isValidForMetrics(TldServiceState state) {
if (state.tld() == null || state.status() == null) {
logger.atSevere().log(
"Contract Violation: Received invalid state (TLD=%s, Status=%s). Skipping.",
state.tld(), state.status());
return false;
}
return true;
}
}

View File

@@ -48,6 +48,7 @@ public class TriggerServiceStateAction implements Runnable {
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.");

View File

@@ -200,7 +200,7 @@ public final class MosApiModule {
@Singleton
@Named("mosapiTldExecutor")
static ExecutorService provideMosapiTldExecutor(
@Config("mosapiTldThreadCnt") int threadPoolSize) {
@Config("mosapiTldThreadCount") int threadPoolSize) {
return Executors.newFixedThreadPool(threadPoolSize);
}
}

View File

@@ -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(

View File

@@ -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)),

View File

@@ -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) {

View File

@@ -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,45 +58,13 @@
<complexType name="objectCDType">
<sequence>
<element name="object">
<complexType>
<sequence>
<any namespace="##other" processContents="lax"/>
</sequence>
</complexType>
</element>
<element name="objID" type="fee:objectIdentifierType" />
<element name="class" type="token" minOccurs="0" />
<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="command" type="fee:commandDataType"
minOccurs="0" maxOccurs="unbounded" />
<element name="credit"
type="fee:creditType"
minOccurs="0" maxOccurs="unbounded" />
<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-->
@@ -119,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"
@@ -161,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>
@@ -184,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" />
@@ -202,6 +186,7 @@
<simpleContent>
<extension base="fee:negativeDecimal">
<attribute name="description"/>
<attribute name="lang" type="language" default="en"/>
</extension>
</simpleContent>
</complexType>

View File

@@ -1,39 +0,0 @@
{
"name": "Wipe Out PII From Old Contact History Entries",
"description": "An Apache Beam batch pipeline that finds old contact history entries and remove PII information from them.",
"parameters": [
{
"name": "registryEnvironment",
"label": "The Registry environment.",
"helpText": "The Registry environment.",
"is_optional": false,
"regexes": [
"^PRODUCTION|SANDBOX|CRASH|QA|ALPHA$"
]
},
{
"name": "isolationOverride",
"label": "The desired SQL transaction isolation level.",
"helpText": "The desired SQL transaction isolation level.",
"is_optional": true,
"regexes": [
"^[0-9A-Z_]+$"
]
},
{
"name": "cutoffTime",
"label": "The maximum history modification time of a contact history entry eligible for wipe out.",
"helpText": "If the history modificaiton time of contact history entry is older than this, and it is not the most recent entry of a contact, it will have its PII wiped out.",
"is_optional": true
},
{
"name": "isDryRun",
"label": "Whether this job is a dry run.",
"helpText": "If true, no changes will be saved to the database.",
"is_optional": true,
"regexes": [
"^true|false$"
]
}
]
}

View File

@@ -1,119 +0,0 @@
// 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.batch;
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.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.newDomain;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.mockito.Mockito.mock;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.util.concurrent.RateLimiter;
import google.registry.flows.DaggerEppTestComponent;
import google.registry.flows.EppController;
import google.registry.flows.EppTestComponent.FakesAndMocksModule;
import google.registry.model.common.FeatureFlag;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeLockHandler;
import google.registry.testing.FakeResponse;
import java.util.Optional;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link RemoveAllDomainContactsAction}. */
class RemoveAllDomainContactsActionTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private final FakeResponse response = new FakeResponse();
private final RateLimiter rateLimiter = mock(RateLimiter.class);
private RemoveAllDomainContactsAction action;
@BeforeEach
void beforeEach() {
createTld("tld");
persistResource(
new FeatureFlag.Builder()
.setFeatureName(MINIMUM_DATASET_CONTACTS_PROHIBITED)
.setStatusMap(ImmutableSortedMap.of(START_OF_TIME, ACTIVE))
.build());
EppController eppController =
DaggerEppTestComponent.builder()
.fakesAndMocksModule(FakesAndMocksModule.create(new FakeClock()))
.build()
.startRequest()
.eppController();
action =
new RemoveAllDomainContactsAction(
eppController, "NewRegistrar", new FakeLockHandler(true), rateLimiter, response);
}
@Test
void test_removesAllContactsFromMultipleDomains_andDoesntModifyDomainThatHasNoContacts() {
Contact c1 = persistActiveContact("contact12345");
Domain d1 = persistResource(newDomain("foo.tld", c1));
assertThat(d1.getAllContacts()).hasSize(3);
Contact c2 = persistActiveContact("contact23456");
Domain d2 = persistResource(newDomain("bar.tld", c2));
assertThat(d2.getAllContacts()).hasSize(3);
Domain d3 =
persistResource(
newDomain("baz.tld")
.asBuilder()
.setRegistrant(Optional.empty())
.setContacts(ImmutableSet.of())
.build());
assertThat(d3.getAllContacts()).isEmpty();
DateTime lastUpdate = d3.getUpdateTimestamp().getTimestamp();
action.run();
assertThat(loadByEntity(d1).getAllContacts()).isEmpty();
assertThat(loadByEntity(d2).getAllContacts()).isEmpty();
assertThat(loadByEntity(d3).getUpdateTimestamp().getTimestamp()).isEqualTo(lastUpdate);
}
@Test
void test_removesContacts_onDomainsThatOnlyPartiallyHaveContacts() {
Contact c1 = persistActiveContact("contact12345");
Domain d1 =
persistResource(
newDomain("foo.tld", c1).asBuilder().setContacts(ImmutableSet.of()).build());
assertThat(d1.getAllContacts()).hasSize(1);
Contact c2 = persistActiveContact("contact23456");
Domain d2 =
persistResource(
newDomain("bar.tld", c2).asBuilder().setRegistrant(Optional.empty()).build());
assertThat(d2.getAllContacts()).hasSize(2);
action.run();
assertThat(loadByEntity(d1).getAllContacts()).isEmpty();
assertThat(loadByEntity(d2).getAllContacts()).isEmpty();
}
}

View File

@@ -1,133 +0,0 @@
// Copyright 2021 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.batch;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.api.services.dataflow.model.LaunchFlexTemplateRequest;
import google.registry.beam.BeamActionTestBase;
import google.registry.testing.FakeClock;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
/** Unit tests for {@link WipeOutContactHistoryPiiAction}. */
class WipeOutContactHistoryPiiActionTest extends BeamActionTestBase {
private final DateTime now = DateTime.parse("2019-01-19T01:02:03Z");
private final FakeClock clock = new FakeClock(now);
private final Map<String, String> expectedParameters = new HashMap<>();
private final ArgumentCaptor<LaunchFlexTemplateRequest> launchRequest =
ArgumentCaptor.forClass(LaunchFlexTemplateRequest.class);
private WipeOutContactHistoryPiiAction action =
new WipeOutContactHistoryPiiAction(
clock,
false,
Optional.empty(),
8,
"tucketBucket",
"testProject",
"testRegion",
dataflow,
response);
@BeforeEach
void before() {
expectedParameters.put("registryEnvironment", "UNITTEST");
expectedParameters.put("isDryRun", "false");
expectedParameters.put("cutoffTime", "2018-05-19T01:02:03.000Z");
}
@Test
void testSuccess() throws Exception {
action.run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload())
.isEqualTo("Launched contact history PII wipeout pipeline: jobid");
verify(templates, times(1))
.launch(eq("testProject"), eq("testRegion"), launchRequest.capture());
assertThat(launchRequest.getValue().getLaunchParameter().getParameters())
.containsExactlyEntriesIn(expectedParameters);
}
@Test
void testSuccess_providedCutoffTime() throws Exception {
action =
new WipeOutContactHistoryPiiAction(
clock,
false,
Optional.of(now.minusYears(1)),
8,
"tucketBucket",
"testProject",
"testRegion",
dataflow,
response);
action.run();
expectedParameters.put("cutoffTime", "2018-01-19T01:02:03.000Z");
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload())
.isEqualTo("Launched contact history PII wipeout pipeline: jobid");
verify(templates, times(1))
.launch(eq("testProject"), eq("testRegion"), launchRequest.capture());
assertThat(launchRequest.getValue().getLaunchParameter().getParameters())
.containsExactlyEntriesIn(expectedParameters);
}
@Test
void testSuccess_dryRun() throws Exception {
action =
new WipeOutContactHistoryPiiAction(
clock,
true,
Optional.empty(),
8,
"tucketBucket",
"testProject",
"testRegion",
dataflow,
response);
action.run();
expectedParameters.put("isDryRun", "true");
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload())
.isEqualTo("Launched contact history PII wipeout pipeline: jobid");
verify(templates, times(1))
.launch(eq("testProject"), eq("testRegion"), launchRequest.capture());
assertThat(launchRequest.getValue().getLaunchParameter().getParameters())
.containsExactlyEntriesIn(expectedParameters);
}
@Test
void testFailure_launchError() throws Exception {
when(launch.execute()).thenThrow(new IOException("cannot launch"));
action.run();
assertThat(response.getStatus()).isEqualTo(500);
assertThat(response.getPayload()).isEqualTo("Pipeline launch failed: cannot launch");
verify(templates, times(1))
.launch(eq("testProject"), eq("testRegion"), launchRequest.capture());
assertThat(launchRequest.getValue().getLaunchParameter().getParameters())
.containsExactlyEntriesIn(expectedParameters);
}
}

View File

@@ -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>

View File

@@ -22,7 +22,6 @@ import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistContactWithPendingTransfer;
import static google.registry.testing.DatabaseHelper.persistDomainWithDependentResources;
import static google.registry.testing.DatabaseHelper.persistDomainWithPendingTransfer;
import static google.registry.testing.DatabaseHelper.persistNewRegistrars;
@@ -89,18 +88,6 @@ public class ResaveAllEppResourcesPipelineTest {
assertThat(loadByEntity(contact)).isEqualTo(contact);
}
@Test
void testPipeline_fulfilledContactTransfer() {
Contact contact = persistActiveContact("test123");
DateTime now = fakeClock.nowUtc();
contact = persistContactWithPendingTransfer(contact, now, now.plusDays(5), now);
fakeClock.advanceBy(Duration.standardDays(10));
assertThat(loadByEntity(contact).getStatusValues()).contains(StatusValue.PENDING_TRANSFER);
runPipeline();
assertThat(loadByEntity(contact).getStatusValues())
.doesNotContain(StatusValue.PENDING_TRANSFER);
}
@Test
void testPipeline_fulfilledDomainTransfer() {
options.setFast(true);

View File

@@ -1,196 +0,0 @@
// Copyright 2023 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.beam.wipeout;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_CREATE;
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ;
import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.testing.DatabaseHelper.newContact;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.hibernate.cfg.AvailableSettings.ISOLATION;
import com.google.common.collect.ImmutableList;
import google.registry.beam.TestPipelineExtension;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactPhoneNumber;
import google.registry.model.reporting.HistoryEntryDao;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.FakeClock;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link WipeOutContactHistoryPiiPipeline}. */
public class WipeOutContactHistoryPiiPipelineTest {
private static final int MIN_AGE_IN_MONTHS = 18;
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
private final FakeClock clock = new FakeClock(DateTime.parse("2020-02-02T12:34:56Z"));
private final WipeOutContactHistoryPiiPipelineOptions options =
PipelineOptionsFactory.create().as(WipeOutContactHistoryPiiPipelineOptions.class);
private Contact contact1;
private Contact contact2;
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder()
.withClock(clock)
.withProperty(ISOLATION, TRANSACTION_REPEATABLE_READ.name())
.buildIntegrationTestExtension();
@RegisterExtension
final TestPipelineExtension pipeline =
TestPipelineExtension.create().enableAbandonedNodeEnforcement(true);
@BeforeEach
void beforeEach() {
contact1 =
persistResource(
newContact("my-contact1")
.asBuilder()
.setEmailAddress("test@example.com")
.setFaxNumber(
new ContactPhoneNumber.Builder().setPhoneNumber("+12122122122").build())
.build());
contact2 =
persistResource(
newContact("my-contact2")
.asBuilder()
.setEmailAddress("test@example.tld")
.setVoiceNumber(
new ContactPhoneNumber.Builder().setPhoneNumber("+19177199177").build())
.build());
// T = 0 month;
persistResource(createHistory(contact1));
// T = 5 months;
advanceMonths(5);
persistResource(createHistory(contact2));
// T = 10 months;
advanceMonths(5);
persistResource(createHistory(contact1));
persistResource(createHistory(contact2));
// T = 20 months;
advanceMonths(10);
persistResource(createHistory(contact2));
// T = 30 months;
advanceMonths(10);
options.setCutoffTime(DATE_TIME_FORMATTER.print(clock.nowUtc().minusMonths(MIN_AGE_IN_MONTHS)));
}
@Test
void testSuccess() {
// Before the pipeline runs, every history entry should have an emali address.
assertThat(
loadAllOf(ContactHistory.class).stream()
.filter(e -> e.getContactBase().get().getEmailAddress() != null)
.count())
.isEqualTo(5);
// Before the pipeline runs, contact history for contact1 should have fax numbers.
ImmutableList<ContactHistory> histories =
HistoryEntryDao.loadHistoryObjectsForResource(contact1.createVKey(), ContactHistory.class);
assertThat(
histories.stream().filter(e -> e.getContactBase().get().getFaxNumber() != null).count())
.isEqualTo(2);
// Before the pipeline runs, contact history for contact2 should have voice numbers.
histories =
HistoryEntryDao.loadHistoryObjectsForResource(contact2.createVKey(), ContactHistory.class);
assertThat(
histories.stream()
.filter(e -> e.getContactBase().get().getVoiceNumber() != null)
.count())
.isEqualTo(3);
WipeOutContactHistoryPiiPipeline wipeOutContactHistoryPiiPipeline =
new WipeOutContactHistoryPiiPipeline(options);
wipeOutContactHistoryPiiPipeline.run(pipeline).waitUntilFinish();
histories =
HistoryEntryDao.loadHistoryObjectsForResource(contact1.createVKey(), ContactHistory.class);
assertThat(histories.size()).isEqualTo(2);
ImmutableList<ContactHistory> wipedEntries =
histories.stream()
.filter(e -> e.getContactBase().get().getEmailAddress() == null)
.collect(toImmutableList());
// Only the history entry at T = 10 is wiped. The one at T = 10 is over 18 months old, but it
// is the most recent entry, so it is kept.
assertThat(wipedEntries.size()).isEqualTo(1);
assertThat(wipedEntries.get(0).getContactBase().get().getFaxNumber()).isNull();
// With a new history entry at T = 30, the one at T = 10 is eligible for wipe out. Note the
// current time itself (therefore the cutoff time) has not changed.
persistResource(createHistory(contact1));
wipeOutContactHistoryPiiPipeline.run(pipeline).waitUntilFinish();
histories =
HistoryEntryDao.loadHistoryObjectsForResource(contact1.createVKey(), ContactHistory.class);
assertThat(histories.size()).isEqualTo(3);
wipedEntries =
histories.stream()
.filter(e -> e.getContactBase().get().getEmailAddress() == null)
.collect(toImmutableList());
assertThat(wipedEntries.size()).isEqualTo(2);
// Check that the pipeline deals with multiple contacts correctly.
histories =
HistoryEntryDao.loadHistoryObjectsForResource(contact2.createVKey(), ContactHistory.class);
assertThat(histories.size()).isEqualTo(3);
wipedEntries =
histories.stream()
.filter(e -> e.getContactBase().get().getEmailAddress() == null)
.collect(toImmutableList());
// Only the history entry at T = 10 is wiped. The one at T = 10 is over 18 months old, but it
// is the most recent entry, so it is kept.
assertThat(wipedEntries.size()).isEqualTo(2);
assertThat(wipedEntries.get(0).getContactBase().get().getVoiceNumber()).isNull();
assertThat(wipedEntries.get(1).getContactBase().get().getVoiceNumber()).isNull();
}
@Test
void testSuccess_dryRun() {
options.setIsDryRun(true);
WipeOutContactHistoryPiiPipeline wipeOutContactHistoryPiiPipeline =
new WipeOutContactHistoryPiiPipeline(options);
wipeOutContactHistoryPiiPipeline.run(pipeline).waitUntilFinish();
ImmutableList<ContactHistory> histories =
HistoryEntryDao.loadHistoryObjectsForResource(contact1.createVKey(), ContactHistory.class);
assertThat(histories.size()).isEqualTo(2);
assertThat(
histories.stream()
.filter(e -> e.getContactBase().get().getEmailAddress() == null)
.collect(toImmutableList()))
.isEmpty();
}
private ContactHistory createHistory(Contact contact) {
return new ContactHistory.Builder()
.setContact(contact)
.setType(CONTACT_CREATE)
.setRegistrarId("TheRegistrar")
.setModificationTime(clock.nowUtc())
.build();
}
private void advanceMonths(int months) {
DateTime now = clock.nowUtc();
DateTime next = now.plusMonths(months);
clock.advanceBy(new Duration(now, next));
}
}

View File

@@ -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())));

View File

@@ -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);

View File

@@ -24,13 +24,10 @@ import com.google.common.collect.Iterables;
import com.google.common.testing.TestLogHandler;
import google.registry.model.EppResource;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.contact.ContactBase;
import google.registry.model.contact.ContactHistory;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.eppinput.EppInput.ResourceCommandWrapper;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.host.HostBase;
import google.registry.model.host.HostHistory;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tmch.ClaimsList;
@@ -131,18 +128,12 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
protected void assertLastHistoryContainsResource(EppResource resource) {
HistoryEntry historyEntry = Iterables.getLast(DatabaseHelper.getHistoryEntries(resource));
if (resource instanceof ContactBase) {
ContactHistory contactHistory = (ContactHistory) historyEntry;
// Don't use direct equals comparison since one might be a subclass of the other
assertAboutImmutableObjects()
.that(contactHistory.getContactBase().get())
.hasFieldsEqualTo(resource);
} else if (resource instanceof DomainBase) {
if (resource instanceof DomainBase) {
DomainHistory domainHistory = (DomainHistory) historyEntry;
assertAboutImmutableObjects()
.that(domainHistory.getDomainBase().get())
.isEqualExceptFields(resource, "gracePeriods", "dsData", "nsHosts");
} else if (resource instanceof HostBase) {
} else {
HostHistory hostHistory = (HostHistory) historyEntry;
// Don't use direct equals comparison since one might be a subclass of the other
assertAboutImmutableObjects()

View File

@@ -123,11 +123,13 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
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");
ImmutableMap.of("FEE_VERSION", "fee-0.6", "FEE_NS", "fee");
private static final ImmutableMap<String, String> FEE_11_MAP =
ImmutableMap.of("FEE_VERSION", "0.11", "FEE_NS", "fee11");
ImmutableMap.of("FEE_VERSION", "fee-0.11", "FEE_NS", "fee11");
private static final ImmutableMap<String, String> FEE_12_MAP =
ImmutableMap.of("FEE_VERSION", "0.12", "FEE_NS", "fee12");
ImmutableMap.of("FEE_VERSION", "fee-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 +398,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 +484,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 +527,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 +547,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 +1207,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 +1214,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
@@ -1330,4 +1240,144 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
+ "history_registrar_id,history_modification_time,history_other_registrar_id,"
+ "history_period_unit,history_period_value,history_reason,history");
}
@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 {
setEppInput("domain_delete.xml", FEE_11_MAP);
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);
}
@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);
}
@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());
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());
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());
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());
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));
}
@Test
void testSuccess_freeCreation_deletionDuringGracePeriod_v12() 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 =
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"));
}
}

View File

@@ -586,6 +586,115 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testIcannActivityReportField_getsLogged() throws Exception {
persistTestEntities(false);
runFlow();
assertIcannReportingActivityFieldLogged("srs-dom-info");
assertTldsFieldLogged("tld");
}
@Test
void testBulkInfoExtension_returnsBulkInfo() throws Exception {
persistTestEntities(false);
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, 0))
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE))
.setDiscountFraction(1.0)
.build());
domain = domain.asBuilder().setCurrentBulkToken(token.createVKey()).build();
persistResource(domain);
setEppInput("domain_info_bulk.xml");
doSuccessfulTest("domain_info_response_bulk.xml", false);
}
@Test
void testBulkInfoExtension_returnsBulkInfoForSuperUser() throws Exception {
persistTestEntities(false);
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, 0))
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE))
.setDiscountFraction(1.0)
.build());
domain = domain.asBuilder().setCurrentBulkToken(token.createVKey()).build();
persistResource(domain);
sessionMetadata.setRegistrarId("TheRegistrar");
setEppInput("domain_info_bulk.xml");
EppOutput output = runFlowAsSuperuser();
String expectedOutput =
loadFile(
"domain_info_response_superuser_bulk.xml",
updateSubstitutions(ImmutableMap.of(), "ROID", "2FF-TLD"));
assertXmlEquals(expectedOutput, new String(marshal(output, ValidationMode.LENIENT), UTF_8));
}
@Test
void testBulkInfoExtension_nameNotInBulkPackage() throws Exception {
setEppInput("domain_info_bulk.xml");
doSuccessfulTest("domain_info_response_empty_bulk_package.xml");
}
@Test
void testBulkInfoExtension_notCurrentSponsorRegistrar() throws Exception {
persistTestEntities(false);
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, 0))
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE))
.setDiscountFraction(1.0)
.build());
domain = domain.asBuilder().setCurrentBulkToken(token.createVKey()).build();
persistResource(domain);
sessionMetadata.setRegistrarId("TheRegistrar");
setEppInput("domain_info_bulk.xml");
doSuccessfulTest("domain_info_response_unauthorized.xml", false);
}
// The fee extension is no longer supported in domain:info commands as of version 1.0.
// For now, we still support old versions.
@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.
@@ -674,26 +783,6 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
"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 {
@@ -867,16 +956,6 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
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() {
@@ -934,89 +1013,12 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
}
@Test
void testIcannActivityReportField_getsLogged() throws Exception {
persistTestEntities(false);
runFlow();
assertIcannReportingActivityFieldLogged("srs-dom-info");
assertTldsFieldLogged("tld");
}
@Test
void testBulkInfoExtension_returnsBulkInfo() throws Exception {
persistTestEntities(false);
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, 0))
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE))
.setDiscountFraction(1.0)
.build());
domain = domain.asBuilder().setCurrentBulkToken(token.createVKey()).build();
persistResource(domain);
setEppInput("domain_info_bulk.xml");
doSuccessfulTest("domain_info_response_bulk.xml", false);
}
@Test
void testBulkInfoExtension_returnsBulkInfoForSuperUser() throws Exception {
persistTestEntities(false);
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, 0))
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE))
.setDiscountFraction(1.0)
.build());
domain = domain.asBuilder().setCurrentBulkToken(token.createVKey()).build();
persistResource(domain);
sessionMetadata.setRegistrarId("TheRegistrar");
setEppInput("domain_info_bulk.xml");
EppOutput output = runFlowAsSuperuser();
String expectedOutput =
loadFile(
"domain_info_response_superuser_bulk.xml",
updateSubstitutions(ImmutableMap.of(), "ROID", "2FF-TLD"));
assertXmlEquals(expectedOutput, new String(marshal(output, ValidationMode.LENIENT), UTF_8));
}
@Test
void testBulkInfoExtension_nameNotInBulkPackage() throws Exception {
setEppInput("domain_info_bulk.xml");
doSuccessfulTest("domain_info_response_empty_bulk_package.xml");
}
@Test
void testBulkInfoExtension_notCurrentSponsorRegistrar() throws Exception {
persistTestEntities(false);
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, 0))
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE))
.setDiscountFraction(1.0)
.build());
domain = domain.asBuilder().setCurrentBulkToken(token.createVKey()).build();
persistResource(domain);
sessionMetadata.setRegistrarId("TheRegistrar");
setEppInput("domain_info_bulk.xml");
doSuccessfulTest("domain_info_response_unauthorized.xml", false);
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();
}
}

View File

@@ -120,11 +120,13 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
"CURRENCY", "USD");
private static final ImmutableMap<String, String> FEE_06_MAP =
updateSubstitutions(FEE_BASE_MAP, "FEE_VERSION", "0.6", "FEE_NS", "fee");
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", "0.11", "FEE_NS", "fee11");
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", "0.12", "FEE_NS", "fee12");
updateSubstitutions(FEE_BASE_MAP, "FEE_VERSION", "fee-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 +392,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 +400,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 +430,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 +438,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 +453,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 +479,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 +919,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 +932,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 +952,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 +1368,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 +1402,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");
@@ -1661,6 +1507,256 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 13.75));
}
@Test
void testSuccess_wrongFeeAmountTooHigh_defaultToken_std_v1() throws Exception {
setEppInput("domain_renew_fee.xml", FEE_STD_1_0_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",
"epp:fee-1.0",
"FEE_NS",
"fee1_00")));
}
@Test
void testFailure_wrongFeeAmountTooLow_defaultToken_std_v1() throws Exception {
setEppInput("domain_renew_fee.xml", FEE_STD_1_0_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_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);
@@ -1694,7 +1790,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
"CURRENCY",
"USD",
"FEE_VERSION",
"0.6",
"fee-0.6",
"FEE_NS",
"fee")));
}
@@ -1755,7 +1851,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
"CURRENCY",
"USD",
"FEE_VERSION",
"0.11",
"fee-0.11",
"FEE_NS",
"fee")));
}
@@ -1816,7 +1912,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
"CURRENCY",
"USD",
"FEE_VERSION",
"0.12",
"fee-0.12",
"FEE_NS",
"fee")));
}
@@ -1843,4 +1939,166 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
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));
}
}

View File

@@ -87,11 +87,13 @@ import org.junit.jupiter.api.Test;
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");
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", "0.11", "FEE_NS", "fee11", "CURRENCY", "USD");
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", "0.12", "FEE_NS", "fee12", "CURRENCY", "USD");
ImmutableMap.of("FEE_VERSION", "fee-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 +310,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 +318,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 +350,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 +376,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 +406,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 +460,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 +488,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 +616,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());
@@ -805,4 +683,222 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
assertThat(thrown).hasMessageThat().contains("domain restore reports are not supported");
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@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();
}
@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();
}
}

View File

@@ -64,7 +64,6 @@ import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
import google.registry.model.billing.BillingCancellation;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingRecurrence;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainHistory;
@@ -153,12 +152,6 @@ class DomainTransferApproveFlowTest
.build());
}
private void setEppLoader(String commandFilename) {
setEppInput(commandFilename);
// Replace the ROID in the xml file with the one generated in our test.
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
}
/**
* Runs a successful test, with the expectedCancellationBillingEvents parameter containing a list
* of billing event builders that will be filled out with the correct HistoryEntry parent as it is
@@ -184,7 +177,7 @@ class DomainTransferApproveFlowTest
String expectedXmlFilename,
DateTime expectedExpirationTime)
throws Exception {
setEppLoader(commandFilename);
setEppInput(commandFilename);
Tld registry = Tld.get(tld);
domain = reloadResourceByForeignKey();
// Make sure the implicit billing event is there; it will be deleted by the flow.
@@ -361,7 +354,7 @@ class DomainTransferApproveFlowTest
}
private void doFailingTest(String commandFilename) throws Exception {
setEppLoader(commandFilename);
setEppInput(commandFilename);
// Setup done; run the test.
assertMutatingFlow(true);
runFlow();
@@ -376,7 +369,7 @@ class DomainTransferApproveFlowTest
@Test
void testDryRun() throws Exception {
setEppLoader("domain_transfer_approve.xml");
setEppInput("domain_transfer_approve.xml");
dryRunFlowAssertResponse(loadFile("domain_transfer_approve_response.xml"));
}
@@ -492,14 +485,6 @@ class DomainTransferApproveFlowTest
"domain_transfer_approve_response.xml");
}
@Test
void testSuccess_contactAuthInfo() throws Exception {
doSuccessfulTest(
"tld",
"domain_transfer_approve_contact_authinfo.xml",
"domain_transfer_approve_response.xml");
}
@Test
void testSuccess_autorenewBeforeTransfer() throws Exception {
domain = reloadResourceByForeignKey();
@@ -619,14 +604,8 @@ class DomainTransferApproveFlowTest
}
@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());
void testFailure_contactPassword() {
// Contact passwords cannot be provided because we don't store contacts
EppException thrown =
assertThrows(
BadAuthInfoForResourceException.class,

View File

@@ -43,7 +43,6 @@ import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException;
import google.registry.flows.exceptions.NotPendingTransferException;
import google.registry.flows.exceptions.NotTransferInitiatorException;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainHistory;
@@ -75,8 +74,6 @@ class DomainTransferCancelFlowTest
private void doSuccessfulTest(String commandFilename) throws Exception {
setEppInput(commandFilename);
// Replace the ROID in the xml file with the one generated in our test.
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
// Make sure the implicit billing event is there; it will be deleted by the flow.
// We also expect to see autorenew events for the gaining and losing registrars.
assertBillingEvents(
@@ -187,8 +184,6 @@ class DomainTransferCancelFlowTest
private void doFailingTest(String commandFilename) throws Exception {
setEppInput(commandFilename);
// 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();
@@ -204,7 +199,6 @@ class DomainTransferCancelFlowTest
@Test
void testDryRun() throws Exception {
setEppInput("domain_transfer_cancel.xml");
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
dryRunFlowAssertResponse(loadFile("domain_transfer_cancel_response.xml"));
}
@@ -219,19 +213,8 @@ class DomainTransferCancelFlowTest
}
@Test
void testSuccess_contactAuthInfo() throws Exception {
doSuccessfulTest("domain_transfer_cancel_contact_authinfo.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());
void testFailure_contactPassword() {
// Contact passwords cannot be provided because we don't store contacts
EppException thrown =
assertThrows(
BadAuthInfoForResourceException.class,

View File

@@ -20,7 +20,6 @@ import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
import static google.registry.testing.DatabaseHelper.createBillingEventForTransfer;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistDomainWithDependentResources;
import static google.registry.testing.DatabaseHelper.persistDomainWithPendingTransfer;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -36,7 +35,6 @@ import google.registry.model.billing.BillingBase.Flag;
import google.registry.model.billing.BillingBase.Reason;
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.eppcommon.StatusValue;
@@ -72,7 +70,6 @@ abstract class DomainTransferFlowTestCase<F extends Flow, R extends EppResource>
static final DateTime EXTENDED_REGISTRATION_EXPIRATION_TIME =
REGISTRATION_EXPIRATION_TIME.plusYears(EXTENDED_REGISTRATION_YEARS);
protected Contact contact;
protected Domain domain;
Host subordinateHost;
private DomainHistory historyEntryDomainCreate;
@@ -104,12 +101,11 @@ abstract class DomainTransferFlowTestCase<F extends Flow, R extends EppResource>
/** 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,
null,
clock.nowUtc(),
DateTime.parse("1999-04-03T22:00:00.0Z"),
REGISTRATION_EXPIRATION_TIME);

View File

@@ -29,7 +29,6 @@ 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.ContactAuthInfo;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
@@ -52,8 +51,6 @@ class DomainTransferQueryFlowTest
private void doSuccessfulTest(
String commandFilename, String expectedXmlFilename, int numPollMessages) throws Exception {
setEppInput(commandFilename);
// 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(false);
runFlowAssertResponse(loadFile(expectedXmlFilename));
@@ -73,8 +70,6 @@ class DomainTransferQueryFlowTest
private void doFailingTest(String commandFilename) throws Exception {
setEppInput(commandFilename);
// 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(false);
runFlow();
@@ -105,13 +100,6 @@ class DomainTransferQueryFlowTest
"domain_transfer_query_domain_authinfo.xml", "domain_transfer_query_response.xml", 1);
}
@Test
void testSuccess_contactAuthInfo() throws Exception {
setRegistrarIdForFlow("ClientZ");
doSuccessfulTest(
"domain_transfer_query_contact_authinfo.xml", "domain_transfer_query_response.xml", 1);
}
@Test
void testSuccess_clientApproved() throws Exception {
changeTransferStatus(TransferStatus.CLIENT_APPROVED);
@@ -170,14 +158,7 @@ class DomainTransferQueryFlowTest
}
@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());
void testFailure_contactPasswordNotAllowed() {
EppException thrown =
assertThrows(
BadAuthInfoForResourceException.class,

View File

@@ -43,7 +43,6 @@ import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
import google.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException;
import google.registry.flows.exceptions.NotPendingTransferException;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainHistory;
@@ -78,7 +77,6 @@ class DomainTransferRejectFlowTest
private void doSuccessfulTest(String commandFilename, String expectedXmlFilename)
throws Exception {
setEppInput(commandFilename);
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
// Make sure the implicit billing event is there; it will be deleted by the flow.
// We also expect to see autorenew events for the gaining and losing registrars.
assertBillingEvents(
@@ -149,8 +147,6 @@ class DomainTransferRejectFlowTest
private void doFailingTest(String commandFilename) throws Exception {
setEppInput(commandFilename);
// 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();
@@ -171,7 +167,6 @@ class DomainTransferRejectFlowTest
@Test
void testDryRun() throws Exception {
setEppInput("domain_transfer_reject.xml");
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
dryRunFlowAssertResponse(loadFile("domain_transfer_reject_response.xml"));
}
@@ -181,12 +176,6 @@ class DomainTransferRejectFlowTest
"domain_transfer_reject_domain_authinfo.xml", "domain_transfer_reject_response.xml");
}
@Test
void testSuccess_contactAuthInfo() throws Exception {
doSuccessfulTest(
"domain_transfer_reject_contact_authinfo.xml", "domain_transfer_reject_response.xml");
}
@Test
void testFailure_notAuthorizedForTld() {
persistResource(
@@ -209,14 +198,8 @@ class DomainTransferRejectFlowTest
}
@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());
void testFailure_contactPassword() {
// Contact passwords cannot be provided because we don't store contacts
EppException thrown =
assertThrows(
BadAuthInfoForResourceException.class,

View File

@@ -40,12 +40,12 @@ import static google.registry.testing.DatabaseHelper.loadByEntity;
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.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.testing.TestDataHelper.updateSubstitutions;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.JPY;
import static org.joda.money.CurrencyUnit.USD;
@@ -95,7 +95,6 @@ import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
import google.registry.model.billing.BillingCancellation;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingRecurrence;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainHistory;
@@ -149,29 +148,31 @@ class DomainTransferRequestFlowTest
private static final ImmutableMap<String, String> FEE_06_MAP =
new ImmutableMap.Builder<String, String>()
.putAll(BASE_FEE_MAP)
.put("FEE_VERSION", "0.6")
.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", "0.11")
.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", "0.12")
.put("FEE_VERSION", "fee-0.12")
.put("FEE_NS", "fee12")
.build();
private static final ImmutableMap<String, String> FEE_STD_1_0_MAP =
updateSubstitutions(BASE_FEE_MAP, "FEE_VERSION", "epp:fee-1.0", "FEE_NS", "fee1_00");
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", "0.12")
.put("FEE_NS", "fee12")
.put("FEE_VERSION", "epp:fee-1.0")
.put("FEE_NS", "fee")
.build();
@BeforeEach
@@ -467,8 +468,6 @@ class DomainTransferRequestFlowTest
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.
@@ -568,8 +567,6 @@ class DomainTransferRequestFlowTest
eppRequestSource = EppRequestSource.TOOL;
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.
@@ -623,8 +620,6 @@ class DomainTransferRequestFlowTest
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);
@@ -654,7 +649,6 @@ class DomainTransferRequestFlowTest
void testDryRun() throws Exception {
setupDomain("example", "tld");
setEppInput("domain_transfer_request.xml");
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
dryRunFlowAssertResponse(loadFile("domain_transfer_request_response.xml"));
}
@@ -665,140 +659,50 @@ class DomainTransferRequestFlowTest
}
@Test
void testSuccess_fee_v06() throws Exception {
void testSuccess_fee_std_v1() throws Exception {
setupDomain("example", "tld");
doSuccessfulTest(
"domain_transfer_request_fee.xml", "domain_transfer_request_response_fee.xml", FEE_06_MAP);
"domain_transfer_request_fee.xml",
"domain_transfer_request_response_fee.xml",
FEE_STD_1_0_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_fee_withDefaultAttributes_v06() throws Exception {
void testSuccess_fee_withDefaultAttributes_std_v1() throws Exception {
setupDomain("example", "tld");
doSuccessfulTest(
"domain_transfer_request_fee_defaults.xml",
"domain_transfer_request_response_fee.xml",
FEE_06_MAP);
FEE_STD_1_0_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() {
void testFailure_refundableFee_std_v1() {
setupDomain("example", "tld");
EppException thrown =
assertThrows(
UnsupportedFeeAttributeException.class,
() -> doFailingTest("domain_transfer_request_fee_refundable.xml", FEE_06_MAP));
() -> doFailingTest("domain_transfer_request_fee_refundable.xml", FEE_STD_1_0_MAP));
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_refundableFee_v11() {
void testFailure_gracePeriodFee_std_v1() {
setupDomain("example", "tld");
EppException thrown =
assertThrows(
UnsupportedFeeAttributeException.class,
() -> doFailingTest("domain_transfer_request_fee_refundable.xml", FEE_11_MAP));
() -> doFailingTest("domain_transfer_request_fee_grace_period.xml", FEE_STD_1_0_MAP));
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_refundableFee_v12() {
void testFailure_appliedFee_std_v1() {
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_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 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));
() -> doFailingTest("domain_transfer_request_fee_applied.xml", FEE_STD_1_0_MAP));
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@@ -991,7 +895,7 @@ class DomainTransferRequestFlowTest
}
@Test
void testFailure_superuserExtension_zeroPeriod_feeTransferExtension() {
void testFailure_superuserExtension_zeroPeriod_feeTransferExtension_std_v1() {
setupDomain("example", "tld");
eppRequestSource = EppRequestSource.TOOL;
clock.advanceOneMilli();
@@ -1001,7 +905,11 @@ class DomainTransferRequestFlowTest
runTest(
"domain_transfer_request_fee_and_superuser_extension.xml",
UserPrivileges.SUPERUSER,
ImmutableMap.of("PERIOD", "0", "AUTOMATIC_TRANSFER_LENGTH", "5")));
new ImmutableMap.Builder<String, String>()
.putAll(FEE_STD_1_0_MAP)
.put("PERIOD", "0")
.put("AUTOMATIC_TRANSFER_LENGTH", "5")
.build()));
}
@Test
@@ -1063,7 +971,7 @@ class DomainTransferRequestFlowTest
}
@Test
void testSuccess_customLogicFee() throws Exception {
void testSuccess_customLogicFee_std_v1() throws Exception {
setupDomain("expensive-domain", "foo");
clock.advanceOneMilli();
doSuccessfulTest(
@@ -1075,7 +983,7 @@ class DomainTransferRequestFlowTest
.put("YEARS", "1")
.put("AMOUNT", "111.00")
.put("EXDATE", "2002-09-08T22:00:00.0Z")
.put("FEE_VERSION", "0.6")
.put("FEE_VERSION", "epp:fee-1.0")
.put("FEE_NS", "fee")
.build(),
Optional.of(Money.of(USD, 111)));
@@ -1227,7 +1135,7 @@ class DomainTransferRequestFlowTest
}
@Test
void testSuccess_premiumNotBlocked() throws Exception {
void testSuccess_premiumNotBlocked_v06() throws Exception {
setupDomain("rich", "example");
clock.advanceOneMilli();
// We don't verify the results; just check that the flow doesn't fail.
@@ -1235,7 +1143,7 @@ class DomainTransferRequestFlowTest
}
@Test
void testSuccess_premiumNotBlockedInSuperuserMode() throws Exception {
void testSuccess_premiumNotBlockedInSuperuserMode_std_v1() throws Exception {
setupDomain("rich", "example");
clock.advanceOneMilli();
// Modify the Registrar to block premium names.
@@ -1267,7 +1175,6 @@ class DomainTransferRequestFlowTest
// This ensures that the transfer has non-premium cost, as otherwise, the fee extension would be
// required to ack the premium price.
setEppInput("domain_transfer_request.xml");
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
runFlowAssertResponse(loadFile("domain_transfer_request_response.xml"));
domain = loadByEntity(domain);
@@ -1321,7 +1228,6 @@ class DomainTransferRequestFlowTest
DateTime now = clock.nowUtc();
setEppInput("domain_transfer_request.xml");
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
runFlowAssertResponse(loadFile("domain_transfer_request_response.xml"));
domain = loadByEntity(domain);
@@ -1383,7 +1289,6 @@ class DomainTransferRequestFlowTest
DateTime now = clock.nowUtc();
setEppInput("domain_transfer_request.xml");
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
runFlowAssertResponse(loadFile("domain_transfer_request_response.xml"));
domain = loadByEntity(domain);
@@ -1444,7 +1349,6 @@ class DomainTransferRequestFlowTest
DateTime now = clock.nowUtc();
setEppInput("domain_transfer_request.xml");
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
runFlowAssertResponse(loadFile("domain_transfer_request_response.xml"));
domain = loadByEntity(domain);
@@ -1525,24 +1429,14 @@ class DomainTransferRequestFlowTest
}
@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);
void testFailure_wrongCurrency_std_v1() {
runWrongCurrencyTest(FEE_STD_1_0_MAP);
}
@Test
void testFailure_unknownCurrency() {
Map<String, String> substitutions = Maps.newHashMap();
substitutions.putAll(FEE_06_MAP);
substitutions.putAll(FEE_STD_1_0_MAP);
substitutions.put("CURRENCY", "BAD");
setupDomain("example", "tld");
setEppInput("domain_transfer_request_fee.xml", substitutions);
@@ -1551,32 +1445,12 @@ class DomainTransferRequestFlowTest
}
@Test
void testFailure_feeGivenInWrongScale_v06() {
void testFailure_feeGivenInWrongScale_std_v1() {
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));
() -> doFailingTest("domain_transfer_request_fee_bad_scale.xml", FEE_STD_1_0_MAP));
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@@ -1594,21 +1468,9 @@ class DomainTransferRequestFlowTest
}
@Test
void testFailure_wrongFeeAmount_v06() {
void testFailure_wrongFeeAmount_std_v1() {
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);
runWrongFeeAmountTest(FEE_STD_1_0_MAP);
}
@Test
@@ -1643,36 +1505,6 @@ class DomainTransferRequestFlowTest
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_badContactPassword() {
setupDomain("example", "tld");
// 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("domain_transfer_request.xml"));
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_badContactRepoId() {
setupDomain("example", "tld");
// 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("domain_transfer_request.xml"));
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testSuccess_clientApproved() throws Exception {
setupDomain("example", "tld");
@@ -1776,7 +1608,6 @@ class DomainTransferRequestFlowTest
setEppInput(
"domain_transfer_request_wildcard.xml",
ImmutableMap.of("YEARS", "1", "DOMAIN", "--invalid", "EXDATE", "2002-09-08T22:00:00.0Z"));
eppLoader.replaceAll("JD1234-REP", contact.getRepoId());
assertMutatingFlow(true);
ResourceDoesNotExistException thrown =
assertThrows(
@@ -1788,7 +1619,6 @@ class DomainTransferRequestFlowTest
@Test
void testFailure_nonexistentDomain() {
createTld("tld");
contact = persistActiveContact("jd1234");
ResourceDoesNotExistException thrown =
assertThrows(
ResourceDoesNotExistException.class,
@@ -1796,6 +1626,22 @@ class DomainTransferRequestFlowTest
assertThat(thrown).hasMessageThat().contains(String.format("(%s)", "example.tld"));
}
@Test
void testFailure_cannotUseContactAuthInfo() {
// RFC 5731: "An OPTIONAL "roid" attribute MUST be used to identify the registrant or contact
// object if and only if the given authInfo is associated with a registrant or contact object,
// and not the domain object itself."
//
// We have no contacts, so it cannot be valid to specify a roid
setupDomain("example", "tld");
assertAboutEppExceptions()
.that(
assertThrows(
BadAuthInfoForResourceException.class,
() -> doFailingTest("domain_transfer_request_contact_auth_info_failure.xml")))
.marshalsToXml();
}
@Test
void testFailure_periodInMonths() {
setupDomain("example", "tld");
@@ -1969,4 +1815,250 @@ class DomainTransferRequestFlowTest
assertThrows(AlreadyRedeemedAllocationTokenException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@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()));
}
}

View File

@@ -63,16 +63,15 @@ import google.registry.config.RegistryConfig;
import google.registry.flows.EppException;
import google.registry.flows.EppException.UnimplementedExtensionException;
import google.registry.flows.EppRequestSource;
import google.registry.flows.FlowTestCase.CommitMode;
import google.registry.flows.FlowTestCase.UserPrivileges;
import google.registry.flows.FlowUtils.NotLoggedInException;
import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.ResourceFlowUtils.AddExistingValueException;
import google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException;
import google.registry.flows.ResourceFlowUtils.RemoveNonexistentValueException;
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
import google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException;
import google.registry.flows.domain.DomainFlowUtils.EmptySecDnsUpdateException;
import google.registry.flows.domain.DomainFlowUtils.FeesMismatchException;
import google.registry.flows.domain.DomainFlowUtils.FeesRequiredForNonFreeOperationException;
import google.registry.flows.domain.DomainFlowUtils.InvalidDsRecordException;
import google.registry.flows.domain.DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException;
@@ -359,10 +358,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
}
}
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setNameservers(nameservers.build())
.build());
reloadResourceByForeignKey().asBuilder().setNameservers(nameservers.build()).build());
clock.advanceOneMilli();
assertMutatingFlow(true);
runFlowAssertResponse(loadFile("generic_success_response.xml"));
@@ -452,6 +448,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
@Test
void testSuccess_removeClientUpdateProhibited() throws Exception {
setEppInput("domain_update_remove_client_update_prohibited.xml");
persistReferencedEntities();
persistResource(
persistDomain()
@@ -547,21 +544,26 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
}
@Test
void testSuccess_secDnsAddSameDoesNothing() throws Exception {
doSecDnsSuccessfulTest(
"domain_update_dsdata_add.xml",
ImmutableSet.of(SOME_DSDATA),
ImmutableSet.of(SOME_DSDATA),
ImmutableMap.of(
"KEY_TAG",
"1",
"ALG",
"2",
"DIGEST_TYPE",
"2",
"DIGEST",
"9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08"),
false);
void testFailure_secDnsAddSame_throwsException() throws Exception {
EppException thrown =
assertThrows(
AddExistingValueException.class,
() ->
doSecDnsSuccessfulTest(
"domain_update_dsdata_add.xml",
ImmutableSet.of(SOME_DSDATA),
ImmutableSet.of(SOME_DSDATA),
ImmutableMap.of(
"KEY_TAG",
"1",
"ALG",
"2",
"DIGEST_TYPE",
"2",
"DIGEST",
"9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08"),
false));
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@@ -679,7 +681,7 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
2,
2,
base16()
.decode("9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08"))),
.decode("0F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08"))),
ImmutableMap.of(
"KEY_TAG",
"1",
@@ -688,8 +690,8 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
"DIGEST_TYPE",
"2",
"DIGEST",
"9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08"),
false);
"0F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08"),
true);
}
@Test
@@ -797,29 +799,43 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
}
@Test
void testSuccess_secDnsAddRemoveSame() throws Exception {
// Adding and removing the same dsData is a no-op because removes are processed first.
doSecDnsSuccessfulTest(
"domain_update_dsdata_add_rem_same.xml",
ImmutableSet.of(
SOME_DSDATA,
DomainDsData.create(
12345, 3, 1, base16().decode("A94A8FE5CCB19BA61C4C0873D391E987982FBBD3"))),
ImmutableSet.of(
SOME_DSDATA,
DomainDsData.create(
12345, 3, 1, base16().decode("A94A8FE5CCB19BA61C4C0873D391E987982FBBD3"))),
false);
void testFailure_secDnsAddRemoveSame_throwsException() throws Exception {
EppException thrown =
assertThrows(
AddRemoveSameValueException.class,
() ->
doSecDnsSuccessfulTest(
"domain_update_dsdata_add_rem_same.xml",
ImmutableSet.of(
SOME_DSDATA,
DomainDsData.create(
12345,
3,
1,
base16().decode("A94A8FE5CCB19BA61C4C0873D391E987982FBBD3"))),
ImmutableSet.of(
SOME_DSDATA,
DomainDsData.create(
12345,
3,
1,
base16().decode("A94A8FE5CCB19BA61C4C0873D391E987982FBBD3"))),
false));
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testSuccess_secDnsRemoveAlreadyNotThere() throws Exception {
// Removing a dsData that isn't there is a no-op.
doSecDnsSuccessfulTest(
"domain_update_dsdata_rem.xml",
ImmutableSet.of(SOME_DSDATA),
ImmutableSet.of(SOME_DSDATA),
false);
void testFailure_secDnsRemoveAlreadyNotThere_throwsException() throws Exception {
EppException thrown =
assertThrows(
RemoveNonexistentValueException.class,
() ->
doSecDnsSuccessfulTest(
"domain_update_dsdata_rem.xml",
ImmutableSet.of(SOME_DSDATA),
ImmutableSet.of(SOME_DSDATA),
false));
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
void doServerStatusBillingTest(String xmlFilename, boolean isBillable) throws Exception {
@@ -854,14 +870,6 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
doServerStatusBillingTest("domain_update_add_server_status.xml", true);
}
@Test
void testSuccess_noBillingOnPreExistingServerStatus() throws Exception {
eppRequestSource = EppRequestSource.TOOL;
Domain addStatusDomain = persistActiveDomain(getUniqueIdFromCommand());
persistResource(addStatusDomain.asBuilder().addStatusValue(SERVER_RENEW_PROHIBITED).build());
doServerStatusBillingTest("domain_update_add_server_status.xml", false);
}
@Test
void testSuccess_removeServerStatusBillingEvent() throws Exception {
eppRequestSource = EppRequestSource.TOOL;
@@ -1373,6 +1381,52 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_addExistingNameserver() throws Exception {
Host host = persistActiveHost("ns2.example.foo");
persistResource(
DatabaseHelper.newDomain(getUniqueIdFromCommand())
.asBuilder()
.setNameservers(ImmutableSet.of(host.createVKey()))
.build());
setEppInput("domain_update_add_nameserver.xml");
assertAboutEppExceptions()
.that(assertThrows(AddExistingValueException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testFailure_addExistingStatusValue() throws Exception {
persistResource(
DatabaseHelper.newDomain(getUniqueIdFromCommand())
.asBuilder()
.setStatusValues(ImmutableSet.of(CLIENT_RENEW_PROHIBITED))
.build());
setEppInput("domain_update_add_non_server_status.xml");
assertAboutEppExceptions()
.that(assertThrows(AddExistingValueException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testFailure_removeNonexistentNameserver() throws Exception {
persistActiveDomain(getUniqueIdFromCommand());
persistActiveHost("ns1.example.foo");
setEppInput("domain_update_remove_nameserver.xml");
assertAboutEppExceptions()
.that(assertThrows(RemoveNonexistentValueException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testFailure_removeNonexistentStatusValue() throws Exception {
persistActiveDomain(getUniqueIdFromCommand());
setEppInput("domain_update_remove_client_update_prohibited.xml");
assertAboutEppExceptions()
.that(assertThrows(RemoveNonexistentValueException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testFailure_minimumDataset_addingNewRegistrantFails() throws Exception {
persistReferencedEntities();
@@ -1476,15 +1530,6 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
assertAboutDomains().that(reloadResourceByForeignKey()).hasExactlyStatusValues(CLIENT_HOLD);
}
@Test
void testFailure_freePremium_wrongFee() throws Exception {
setEppInput("domain_update_fee.xml", ImmutableMap.of("FEE_VERSION", "0.11"));
persistReferencedEntities();
persistDomain();
EppException thrown = assertThrows(FeesMismatchException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
// This test should throw an exception, because the fee extension is required when the fee is not
// zero.
@Test

View File

@@ -0,0 +1,75 @@
// 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 google.registry.model.eppcommon.ProtocolDefinition;
import google.registry.util.RegistryEnvironment;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Class for testing the XML extension definitions loaded in the prod environment. */
public class ProductionSimulatingFeeExtensionsTest {
private RegistryEnvironment previousEnvironment;
@BeforeEach
void beforeEach() {
previousEnvironment = RegistryEnvironment.get();
}
@AfterEach
void afterEach() {
previousEnvironment.setup();
ProtocolDefinition.reloadServiceExtensionUris();
}
@Test
void testNonProdEnvironments() {
for (RegistryEnvironment env : RegistryEnvironment.values()) {
if (env.equals(RegistryEnvironment.PRODUCTION)) {
continue;
}
env.setup();
ProtocolDefinition.reloadServiceExtensionUris();
assertThat(ProtocolDefinition.getVisibleServiceExtensionUris())
.containsExactly(
"urn:ietf:params:xml:ns:launch-1.0",
"urn:ietf:params:xml:ns:rgp-1.0",
"urn:ietf:params:xml:ns:secDNS-1.1",
"urn:ietf:params:xml:ns:fee-0.6",
"urn:ietf:params:xml:ns:fee-0.11",
"urn:ietf:params:xml:ns:fee-0.12",
"urn:ietf:params:xml:ns:epp:fee-1.0");
}
}
@Test
void testProdEnvironment() {
RegistryEnvironment.PRODUCTION.setup();
ProtocolDefinition.reloadServiceExtensionUris();
// prod shouldn't have the fee extension version 1.0
assertThat(ProtocolDefinition.getVisibleServiceExtensionUris())
.containsExactly(
"urn:ietf:params:xml:ns:launch-1.0",
"urn:ietf:params:xml:ns:rgp-1.0",
"urn:ietf:params:xml:ns:secDNS-1.1",
"urn:ietf:params:xml:ns:fee-0.6",
"urn:ietf:params:xml:ns:fee-0.11",
"urn:ietf:params:xml:ns:fee-0.12");
}
}

View File

@@ -45,6 +45,7 @@ import google.registry.flows.host.HostFlowUtils.HostNameNotNormalizedException;
import google.registry.flows.host.HostFlowUtils.HostNameNotPunyCodedException;
import google.registry.flows.host.HostFlowUtils.HostNameTooLongException;
import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException;
import google.registry.flows.host.HostFlowUtils.InvalidHostNameException;
import google.registry.flows.host.HostFlowUtils.LoopbackIpNotValidForHostException;
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException;
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainInPendingDeleteException;
@@ -82,9 +83,14 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, Host> {
}
private void doSuccessfulTest() throws Exception {
doSuccessfulTest("host_create_response.xml", ImmutableMap.of());
}
private void doSuccessfulTest(String responseFile, ImmutableMap<String, String> substitutions)
throws Exception {
clock.advanceOneMilli();
assertMutatingFlow(true);
runFlowAssertResponse(loadFile("host_create_response.xml"));
runFlowAssertResponse(loadFile(responseFile, substitutions));
Host host = reloadResourceByForeignKey();
// Check that the host was created and persisted with a history entry.
assertAboutHosts()
@@ -134,6 +140,28 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, Host> {
assertHostDnsRequests("ns1.example.tld");
}
@Test
void testSuccess_tldWithHyphenOn3And4() throws Exception {
setEppHostCreateInput("ns1.example.zz--main-2262", null);
doSuccessfulTest(
"host_create_response_wildcard.xml",
ImmutableMap.of("HOSTNAME", "ns1.example.zz--main-2262"));
}
@Test
void testFailure_domainWithHyphenOn3And4() throws Exception {
setEppHostCreateInput("ns1.zz--main-2262.tld", null);
EppException thrown = assertThrows(InvalidHostNameException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_hostnameWithHyphenOn3And4() throws Exception {
setEppHostCreateInput("zz--ns1.domain.tld", null);
EppException thrown = assertThrows(InvalidHostNameException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_multipartTLDsAndInvalidHost() {
createTlds("bar.tld", "tld");

View File

@@ -48,7 +48,9 @@ import google.registry.flows.EppException;
import google.registry.flows.EppRequestSource;
import google.registry.flows.FlowUtils.NotLoggedInException;
import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.ResourceFlowUtils.AddExistingValueException;
import google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException;
import google.registry.flows.ResourceFlowUtils.RemoveNonexistentValueException;
import google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException;
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
import google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException;
@@ -522,6 +524,8 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
.asBuilder()
.setSuperordinateDomain(foo.createVKey())
.setLastTransferTime(null)
.setInetAddresses(
ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A")))
.build());
persistResource(foo.asBuilder().setSubordinateHosts(ImmutableSet.of(oldHostName())).build());
clock.advanceOneMilli();
@@ -561,6 +565,8 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
.setSuperordinateDomain(domain.createVKey())
.setLastTransferTime(clock.nowUtc().minusDays(20))
.setLastSuperordinateChange(clock.nowUtc().minusDays(3))
.setInetAddresses(
ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A")))
.build());
DateTime lastTransferTime = host.getLastTransferTime();
persistResource(domain.asBuilder().setSubordinateHosts(ImmutableSet.of(oldHostName())).build());
@@ -598,6 +604,8 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
.setSuperordinateDomain(foo.createVKey())
.setLastTransferTime(lastTransferTime)
.setLastSuperordinateChange(clock.nowUtc().minusDays(3))
.setInetAddresses(
ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A")))
.build());
persistResource(foo.asBuilder().setSubordinateHosts(ImmutableSet.of(oldHostName())).build());
clock.advanceOneMilli();
@@ -632,6 +640,8 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
.setSuperordinateDomain(foo.createVKey())
.setLastTransferTime(lastTransferTime)
.setLastSuperordinateChange(clock.nowUtc().minusDays(10))
.setInetAddresses(
ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A")))
.build());
persistResource(foo.asBuilder().setSubordinateHosts(ImmutableSet.of(oldHostName())).build());
clock.advanceOneMilli();
@@ -666,6 +676,8 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
.setSuperordinateDomain(foo.createVKey())
.setLastTransferTime(null)
.setLastSuperordinateChange(clock.nowUtc().minusDays(3))
.setInetAddresses(
ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A")))
.build());
persistResource(foo.asBuilder().setSubordinateHosts(ImmutableSet.of(oldHostName())).build());
Host renamedHost = doSuccessfulTest();
@@ -687,7 +699,12 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
createTld("foo");
Domain domain = persistActiveDomain("example.foo");
persistResource(
newHost(oldHostName()).asBuilder().setSuperordinateDomain(domain.createVKey()).build());
newHost(oldHostName())
.asBuilder()
.setSuperordinateDomain(domain.createVKey())
.setInetAddresses(
ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A")))
.build());
DateTime lastTransferTime = clock.nowUtc().minusDays(2);
persistResource(
domain
@@ -728,6 +745,8 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
.setSuperordinateDomain(domain.createVKey())
.setLastTransferTime(lastTransferTime)
.setLastSuperordinateChange(clock.nowUtc().minusDays(4))
.setInetAddresses(
ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A")))
.build());
persistResource(
domain
@@ -763,6 +782,8 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
.setSuperordinateDomain(domain.createVKey())
.setLastTransferTime(clock.nowUtc().minusDays(12))
.setLastSuperordinateChange(clock.nowUtc().minusDays(4))
.setInetAddresses(
ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A")))
.build());
domain =
persistResource(
@@ -1007,6 +1028,50 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_addExistingInetAddress() throws Exception {
createTld("tld");
Domain domain = persistActiveDomain("example.tld");
persistResource(
newHost(oldHostName())
.asBuilder()
.setSuperordinateDomain(domain.createVKey())
.setLastTransferTime(clock.nowUtc().minusDays(12))
.setLastSuperordinateChange(clock.nowUtc().minusDays(4))
.setInetAddresses(
ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A")))
.build());
setEppHostUpdateInput(
"ns1.example.tld",
"ns2.example.tld",
"<host:addr ip=\"v6\">1080:0:0:0:8:800:200C:417A</host:addr>",
null);
assertAboutEppExceptions()
.that(assertThrows(AddExistingValueException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testFailure_removeNonexistentInetAddress() throws Exception {
createTld("tld");
Domain domain = persistActiveDomain("example.tld");
persistResource(
newHost(oldHostName())
.asBuilder()
.setSuperordinateDomain(domain.createVKey())
.setLastTransferTime(clock.nowUtc().minusDays(12))
.setLastSuperordinateChange(clock.nowUtc().minusDays(4))
.build());
setEppHostUpdateInput(
"ns1.example.tld",
"ns2.example.tld",
null,
"<host:addr ip=\"v6\">1080:0:0:0:8:800:200C:417A</host:addr>");
assertAboutEppExceptions()
.that(assertThrows(RemoveNonexistentValueException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testSuccess_clientUpdateProhibited_removed() throws Exception {
setEppInput("host_update_remove_client_update_prohibited.xml");
@@ -1078,7 +1143,12 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
setEppInput("host_update_prohibited_status.xml");
createTld("tld");
persistActiveDomain("example.tld");
persistActiveHost("ns1.example.tld");
persistResource(
persistActiveHost("ns1.example.tld")
.asBuilder()
.setInetAddresses(
ImmutableSet.of(InetAddresses.forString("1080:0:0:0:8:800:200C:417A")))
.build());
clock.advanceOneMilli();
runFlowAssertResponse(

View File

@@ -17,7 +17,6 @@ package google.registry.flows.poll;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.createHistoryEntryForEppResource;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -28,7 +27,6 @@ import google.registry.flows.poll.PollAckFlow.InvalidMessageIdException;
import google.registry.flows.poll.PollAckFlow.MessageDoesNotExistException;
import google.registry.flows.poll.PollAckFlow.MissingMessageIdException;
import google.registry.flows.poll.PollAckFlow.NotAuthorizedToAckMessageException;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.poll.PollMessage;
import google.registry.testing.DatabaseHelper;
@@ -43,7 +41,6 @@ class PollAckFlowTest extends FlowTestCase<PollAckFlow> {
private static final long MESSAGE_ID = 3;
private Domain domain;
private Contact contact;
@BeforeEach
void setUp() {
@@ -51,8 +48,7 @@ class PollAckFlowTest extends FlowTestCase<PollAckFlow> {
clock.setTo(DateTime.parse("2011-01-02T01:01:01Z"));
setRegistrarIdForFlow("NewRegistrar");
createTld("example");
contact = persistActiveContact("jd1234");
domain = persistResource(DatabaseHelper.newDomain("test.example", contact));
domain = persistResource(DatabaseHelper.newDomain("test.example"));
}
private void persistOneTimePollMessage(long messageId) {
@@ -85,43 +81,6 @@ class PollAckFlowTest extends FlowTestCase<PollAckFlow> {
dryRunFlowAssertResponse(loadFile("poll_ack_response_empty.xml"));
}
@Test
void testSuccess_contactPollMessage() throws Exception {
setEppInput("poll_ack.xml", ImmutableMap.of("MSGID", "3-2011"));
persistResource(
new PollMessage.OneTime.Builder()
.setId(MESSAGE_ID)
.setRegistrarId(getRegistrarIdForFlow())
.setEventTime(clock.nowUtc().minusDays(1))
.setMsg("Some poll message.")
.setHistoryEntry(createHistoryEntryForEppResource(contact))
.build());
assertMutatingFlow(true);
runFlowAssertResponse(loadFile("poll_ack_response_empty.xml"));
}
@Test
void testFailure_contactPollMessage_withIncorrectYearField() throws Exception {
setEppInput("poll_ack.xml", ImmutableMap.of("MSGID", "3-1999"));
persistResource(
new PollMessage.OneTime.Builder()
.setId(MESSAGE_ID)
.setRegistrarId(getRegistrarIdForFlow())
.setEventTime(clock.nowUtc().minusDays(1))
.setMsg("Some poll message.")
.setHistoryEntry(createHistoryEntryForEppResource(contact))
.build());
assertMutatingFlow(true);
assertThrows(MessageDoesNotExistException.class, this::runFlow);
}
@Test
void testSuccess_messageOnContact() throws Exception {
persistOneTimePollMessage(MESSAGE_ID);
assertMutatingFlow(true);
runFlowAssertResponse(loadFile("poll_ack_response_empty.xml"));
}
@Test
void testSuccess_recentActiveAutorenew() throws Exception {
setEppInput("poll_ack.xml", ImmutableMap.of("MSGID", "3-2010"));
@@ -184,21 +143,6 @@ class PollAckFlowTest extends FlowTestCase<PollAckFlow> {
assertThrows(InvalidMessageIdException.class, this::runFlow);
}
@Test
void testFailure_contactPollMessage_withMissingYearField() throws Exception {
setEppInput("poll_ack.xml", ImmutableMap.of("MSGID", "3"));
persistResource(
new PollMessage.OneTime.Builder()
.setId(MESSAGE_ID)
.setRegistrarId(getRegistrarIdForFlow())
.setEventTime(clock.nowUtc().minusDays(1))
.setMsg("Some poll message.")
.setHistoryEntry(createHistoryEntryForEppResource(contact))
.build());
assertMutatingFlow(true);
assertThrows(InvalidMessageIdException.class, this::runFlow);
}
@Test
void testFailure_invalidId_stringInsteadOfNumeric() throws Exception {
setEppInput("poll_ack.xml", ImmutableMap.of("MSGID", "ABC-12345"));

View File

@@ -16,7 +16,6 @@ package google.registry.flows.poll;
import static google.registry.testing.DatabaseHelper.createHistoryEntryForEppResource;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -27,8 +26,6 @@ import com.google.common.collect.ImmutableList;
import google.registry.flows.EppException;
import google.registry.flows.FlowTestCase;
import google.registry.flows.poll.PollRequestFlow.UnexpectedMessageIdException;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.domain.Domain;
import google.registry.model.eppcommon.Trid;
import google.registry.model.host.Host;
@@ -36,7 +33,6 @@ import google.registry.model.host.HostHistory;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
import google.registry.model.transfer.TransferStatus;
import google.registry.testing.DatabaseHelper;
@@ -48,7 +44,6 @@ import org.junit.jupiter.api.Test;
class PollRequestFlowTest extends FlowTestCase<PollRequestFlow> {
private Domain domain;
private Contact contact;
private Host host;
@BeforeEach
@@ -58,8 +53,7 @@ class PollRequestFlowTest extends FlowTestCase<PollRequestFlow> {
setRegistrarIdForFlow("NewRegistrar");
createTld("example");
persistNewRegistrar("BadRegistrar");
contact = persistActiveContact("jd1234");
domain = persistResource(DatabaseHelper.newDomain("test.example", contact));
domain = persistResource(DatabaseHelper.newDomain("test.example"));
host = persistActiveHost("ns1.test.example");
}
@@ -99,31 +93,6 @@ class PollRequestFlowTest extends FlowTestCase<PollRequestFlow> {
runFlowAssertResponse(loadFile("poll_response_domain_transfer_no_cltrid.xml"));
}
@Test
void testSuccess_contactTransferPending() throws Exception {
setRegistrarIdForFlow("TheRegistrar");
persistResource(
new PollMessage.OneTime.Builder()
.setId(3L)
.setRegistrarId(getRegistrarIdForFlow())
.setEventTime(clock.nowUtc().minusDays(5))
.setMsg("Transfer requested.")
.setResponseData(
ImmutableList.of(
new ContactTransferResponse.Builder()
.setContactId("sh8013")
.setTransferStatus(TransferStatus.PENDING)
.setGainingRegistrarId(getRegistrarIdForFlow())
.setTransferRequestTime(clock.nowUtc().minusDays(5))
.setLosingRegistrarId("NewRegistrar")
.setPendingTransferExpirationTime(clock.nowUtc())
.build()))
.setHistoryEntry(createHistoryEntryForEppResource(contact))
.build());
assertMutatingFlow(false);
runFlowAssertResponse(loadFile("poll_response_contact_transfer.xml"));
}
@Test
void testSuccess_domainPendingActionComplete() throws Exception {
persistResource(
@@ -225,29 +194,6 @@ class PollRequestFlowTest extends FlowTestCase<PollRequestFlow> {
runFlowAssertResponse(loadFile("poll_response_empty.xml"));
}
@Test
void testSuccess_contactDelete() throws Exception {
// Contact delete poll messages do not have any response data, so ensure that no
// response data block is produced in the poll message.
HistoryEntry historyEntry =
persistResource(
new ContactHistory.Builder()
.setRegistrarId("NewRegistrar")
.setModificationTime(clock.nowUtc().minusDays(1))
.setType(HistoryEntry.Type.CONTACT_DELETE)
.setContact(contact)
.build());
persistResource(
new PollMessage.OneTime.Builder()
.setRegistrarId("NewRegistrar")
.setMsg("Deleted contact jd1234")
.setHistoryEntry(historyEntry)
.setEventTime(clock.nowUtc().minusDays(1))
.build());
assertMutatingFlow(false);
runFlowAssertResponse(loadFile("poll_response_contact_delete.xml"));
}
@Test
void testSuccess_hostDelete() throws Exception {
// Host delete poll messages do not have any response data, so ensure that no

View File

@@ -24,12 +24,9 @@ import google.registry.testing.EppLoader;
/** Unit tests for {@code ResourceCommand}. */
public abstract class ResourceCommandTestCase extends EntityTestCase {
protected void doXmlRoundtripTest(String inputFilename, String... ignoredPaths)
throws Exception {
protected void doXmlRoundtripTest(String inputFilename) throws Exception {
EppLoader eppLoader = new EppLoader(this, inputFilename);
assertXmlEquals(
eppLoader.getEppXml(),
new String(marshalInput(eppLoader.getEpp(), STRICT), UTF_8),
ignoredPaths);
eppLoader.getEppXml(), new String(marshalInput(eppLoader.getEpp(), STRICT), UTF_8));
}
}

View File

@@ -113,11 +113,6 @@ class DomainCommandTest extends ResourceCommandTestCase {
doXmlRoundtripTest("domain_update.xml");
}
@Test
void testUpdate_fee() throws Exception {
doXmlRoundtripTest("domain_update_fee.xml");
}
@Test
void testUpdate_emptyCommand() throws Exception {
// This EPP command wouldn't be allowed for policy reasons, but should marshal/unmarshal fine.
@@ -153,11 +148,6 @@ class DomainCommandTest extends ResourceCommandTestCase {
doXmlRoundtripTest("domain_info_sunrise.xml");
}
@Test
void testInfo_feeExtension() throws Exception {
doXmlRoundtripTest("domain_info_fee.xml");
}
@Test
void testCheck() throws Exception {
doXmlRoundtripTest("domain_check.xml");

View File

@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.eppinput.EppInput;
import google.registry.model.eppoutput.EppOutput;
import google.registry.util.RegistryEnvironment;
import org.junit.jupiter.api.Test;
/** Tests for {@link EppXmlTransformer}. */
@@ -38,4 +39,26 @@ class EppXmlTransformerTest {
ClassCastException.class,
() -> unmarshal(EppOutput.class, loadBytes(getClass(), "contact_info.xml").read()));
}
@Test
void testSchemas_inNonProduction_includesFee1Point0() {
var currentEnv = RegistryEnvironment.get();
try {
RegistryEnvironment.SANDBOX.setup();
assertThat(EppXmlTransformer.getSchemas()).contains("fee-std-v1.xsd");
} finally {
currentEnv.setup();
}
}
@Test
void testSchemas_inProduction_skipsFee1Point0() {
var currentEnv = RegistryEnvironment.get();
try {
RegistryEnvironment.PRODUCTION.setup();
assertThat(EppXmlTransformer.getSchemas()).doesNotContain("fee-std-v1.xsd");
} finally {
currentEnv.setup();
}
}
}

View File

@@ -18,7 +18,6 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.poll.PollMessageExternalKeyConverter.makePollMessageExternalId;
import static google.registry.model.poll.PollMessageExternalKeyConverter.parsePollMessageExternalId;
import static google.registry.testing.DatabaseHelper.createTld;
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.persistResource;
@@ -83,23 +82,6 @@ public class PollMessageExternalKeyConverterTest {
assertVKeysEqual(parsePollMessageExternalId("5-2007"), pollMessage.createVKey());
}
@Test
void testSuccess_contact() {
historyEntry =
persistResource(
DatabaseHelper.createHistoryEntryForEppResource(persistActiveContact("tim")));
PollMessage.OneTime pollMessage =
persistResource(
new PollMessage.OneTime.Builder()
.setRegistrarId("TheRegistrar")
.setEventTime(clock.nowUtc())
.setMsg("Test poll message")
.setHistoryEntry(historyEntry)
.build());
assertThat(makePollMessageExternalId(pollMessage)).isEqualTo("7-2007");
assertVKeysEqual(parsePollMessageExternalId("7-2007"), pollMessage.createVKey());
}
@Test
void testSuccess_host() {
historyEntry =

View File

@@ -18,7 +18,6 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -26,13 +25,12 @@ import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableSet;
import google.registry.model.EntityTestCase;
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.domain.Period;
import google.registry.model.eppcommon.Trid;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry.Type;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -40,14 +38,13 @@ import org.junit.jupiter.api.Test;
/** Unit tests for {@link HistoryEntry}. */
class HistoryEntryTest extends EntityTestCase {
private Domain domain;
private DomainHistory domainHistory;
private Contact contact;
@BeforeEach
void setUp() {
createTld("foobar");
Domain domain = persistActiveDomain("foo.foobar");
contact = persistActiveContact("someone");
domain = persistActiveDomain("foo.foobar");
DomainTransactionRecord transactionRecord =
new DomainTransactionRecord.Builder()
.setTld("foobar")
@@ -91,12 +88,12 @@ class HistoryEntryTest extends EntityTestCase {
assertThrows(
IllegalArgumentException.class,
() ->
new ContactHistory.Builder()
new DomainHistory.Builder()
.setRevisionId(5L)
.setModificationTime(DateTime.parse("1985-07-12T22:30:00Z"))
.setRegistrarId("TheRegistrar")
.setReason("Reason")
.setType(HistoryEntry.Type.CONTACT_CREATE)
.setType(Type.DOMAIN_CREATE)
.build());
assertThat(thrown).hasMessageThat().isEqualTo("EPP resource must be specified");
}
@@ -107,10 +104,10 @@ class HistoryEntryTest extends EntityTestCase {
assertThrows(
IllegalArgumentException.class,
() ->
new ContactHistory.Builder()
.setContact(contact)
new DomainHistory.Builder()
.setRevisionId(5L)
.setModificationTime(DateTime.parse("1985-07-12T22:30:00Z"))
.setDomain(domain)
.setModificationTime(DateTime.parse("1985-07-12T22:30.00Z"))
.setRegistrarId("TheRegistrar")
.setReason("Reason")
.build());
@@ -123,12 +120,12 @@ class HistoryEntryTest extends EntityTestCase {
assertThrows(
IllegalArgumentException.class,
() ->
new ContactHistory.Builder()
.setContact(contact)
new DomainHistory.Builder()
.setRevisionId(5L)
.setType(HistoryEntry.Type.CONTACT_CREATE)
.setDomain(domain)
.setType(Type.DOMAIN_CREATE)
.setRegistrarId("TheRegistrar")
.setReason("Reason")
.setReason("reason")
.build());
assertThat(thrown).hasMessageThat().isEqualTo("Modification time must be specified");
}
@@ -139,11 +136,11 @@ class HistoryEntryTest extends EntityTestCase {
assertThrows(
IllegalArgumentException.class,
() ->
new ContactHistory.Builder()
new DomainHistory.Builder()
.setRevisionId(5L)
.setContact(contact)
.setType(HistoryEntry.Type.CONTACT_CREATE)
.setModificationTime(DateTime.parse("1985-07-12T22:30:00Z"))
.setDomain(domain)
.setType(Type.DOMAIN_CREATE)
.setModificationTime(DateTime.parse("1985-07-12T22:30.00Z"))
.setReason("Reason")
.build());
assertThat(thrown).hasMessageThat().isEqualTo("Registrar ID must be specified");
@@ -155,11 +152,11 @@ class HistoryEntryTest extends EntityTestCase {
assertThrows(
IllegalArgumentException.class,
() ->
new ContactHistory.Builder()
.setContact(contact)
new DomainHistory.Builder()
.setRevisionId(5L)
.setType(HistoryEntry.Type.SYNTHETIC)
.setModificationTime(DateTime.parse("1985-07-12T22:30:00Z"))
.setDomain(domain)
.setType(Type.SYNTHETIC)
.setModificationTime(DateTime.parse("1985-07-12T22:30.00Z"))
.setRegistrarId("TheRegistrar")
.setReason("Reason")
.setRequestedByRegistrar(true)

View File

@@ -0,0 +1,96 @@
// 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.monitoring.whitebox;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.monitoring.metrics.MetricRegistry;
import com.google.monitoring.metrics.MetricRegistryImpl;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link JvmMetrics}. */
class JvmMetricsTests {
private MetricRegistry registry;
private JvmMetrics jvmMetrics;
private MemoryMXBean mockMemoryMXBean = mock(MemoryMXBean.class);
private static final MemoryUsage HEAP_USAGE =
new MemoryUsage(/*init*/ 100, /*used*/ 200, /*commited*/ 500, /*max*/ 1000);
private static final MemoryUsage NON_HEAP_USAGE =
new MemoryUsage(/*init*/ 50, /*used*/ 100, /*commited*/ 250, /*max*/ 500);
@BeforeEach
public void setUp() {
MetricRegistryImpl.getDefault().unregisterAllMetrics();
registry = MetricRegistryImpl.getDefault();
when(mockMemoryMXBean.getHeapMemoryUsage()).thenReturn(HEAP_USAGE);
when(mockMemoryMXBean.getNonHeapMemoryUsage()).thenReturn(NON_HEAP_USAGE);
jvmMetrics = new JvmMetrics(mockMemoryMXBean);
}
@Test
public void metricsRegistered() {
jvmMetrics.register();
assertThat(registry.getRegisteredMetrics()).hasSize(3);
for (var metric : registry.getRegisteredMetrics()) {
assertThat(metric).isInstanceOf(com.google.monitoring.metrics.VirtualMetric.class);
}
}
@Test
public void testGetUsedMemory() {
jvmMetrics.register();
ImmutableMap<ImmutableList<String>, Long> values = jvmMetrics.getUsedMemory();
assertThat(values)
.containsExactly(
ImmutableList.of("heap"), 200L,
ImmutableList.of("non_heap"), 100L);
}
@Test
public void testGetCommittedMemory() {
jvmMetrics.register();
ImmutableMap<ImmutableList<String>, Long> values = jvmMetrics.getCommittedMemory();
assertThat(values)
.containsExactly(
ImmutableList.of("heap"), 500L,
ImmutableList.of("non_heap"), 250L);
}
@Test
public void testGetMaxMemory() {
jvmMetrics.register();
ImmutableMap<ImmutableList<String>, Long> values = jvmMetrics.getMaxMemory();
assertThat(values)
.containsExactly(
ImmutableList.of("heap"), 1000L,
ImmutableList.of("non_heap"), 500L);
}
}

View File

@@ -0,0 +1,236 @@
// 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 static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.api.services.monitoring.v3.Monitoring;
import com.google.api.services.monitoring.v3.model.CreateTimeSeriesRequest;
import com.google.api.services.monitoring.v3.model.MetricDescriptor;
import com.google.api.services.monitoring.v3.model.TimeSeries;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.mosapi.MosApiModels.ServiceStatus;
import google.registry.mosapi.MosApiModels.TldServiceState;
import google.registry.request.lock.LockHandler;
import google.registry.testing.FakeClock;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Callable;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
/** Unit tests for {@link MosApiMetrics}. */
public class MosApiMetricsTest {
private static final String PROJECT_ID = "domain-registry-test";
private final Monitoring monitoringClient = mock(Monitoring.class);
private final Monitoring.Projects projects = mock(Monitoring.Projects.class);
private final LockHandler lockHandler = mock(LockHandler.class);
private final Monitoring.Projects.TimeSeries timeSeriesResource =
mock(Monitoring.Projects.TimeSeries.class);
private final Monitoring.Projects.TimeSeries.Create createRequest =
mock(Monitoring.Projects.TimeSeries.Create.class);
// Mocks for Metric Descriptors
private final Monitoring.Projects.MetricDescriptors metricDescriptorsResource =
mock(Monitoring.Projects.MetricDescriptors.class);
private final Monitoring.Projects.MetricDescriptors.Create createDescriptorRequest =
mock(Monitoring.Projects.MetricDescriptors.Create.class);
// Fixed Clock for deterministic testing
private final FakeClock clock = new FakeClock(DateTime.parse("2026-01-01T12:00:00Z"));
private MosApiMetrics mosApiMetrics;
@BeforeEach
void setUp() throws IOException, NoSuchFieldException, IllegalAccessException {
MosApiMetrics.isDescriptorInitialized.set(false);
when(monitoringClient.projects()).thenReturn(projects);
when(projects.timeSeries()).thenReturn(timeSeriesResource);
when(timeSeriesResource.create(anyString(), any(CreateTimeSeriesRequest.class)))
.thenReturn(createRequest);
// Setup for Metric Descriptors
when(projects.metricDescriptors()).thenReturn(metricDescriptorsResource);
when(metricDescriptorsResource.create(anyString(), any(MetricDescriptor.class)))
.thenReturn(createDescriptorRequest);
when(lockHandler.executeWithLocks(any(Callable.class), any(), any(), any()))
.thenAnswer(
invocation -> {
((Callable<?>) invocation.getArgument(0)).call();
return true;
});
mosApiMetrics = new MosApiMetrics(monitoringClient, PROJECT_ID, clock, lockHandler);
}
@Test
void testRecordStates_lazilyInitializesMetricDescriptors() throws IOException {
TldServiceState state = createTldState("test.tld", "UP", "UP");
mosApiMetrics.recordStates(ImmutableList.of(state));
ArgumentCaptor<MetricDescriptor> captor = ArgumentCaptor.forClass(MetricDescriptor.class);
verify(metricDescriptorsResource, times(3))
.create(eq("projects/" + PROJECT_ID), captor.capture());
List<MetricDescriptor> descriptors = captor.getAllValues();
// Verify TLD Status Descriptor
MetricDescriptor tldStatus =
descriptors.stream()
.filter(d -> d.getType().endsWith("tld_status"))
.findFirst()
.orElseThrow();
assertThat(tldStatus.getMetricKind()).isEqualTo("GAUGE");
assertThat(tldStatus.getValueType()).isEqualTo("INT64");
// Verify Service Status Descriptor
MetricDescriptor serviceStatus =
descriptors.stream()
.filter(d -> d.getType().endsWith("service_status"))
.findFirst()
.orElseThrow();
assertThat(serviceStatus.getMetricKind()).isEqualTo("GAUGE");
assertThat(serviceStatus.getValueType()).isEqualTo("INT64");
// Verify Emergency Usage Descriptor
MetricDescriptor emergencyUsage =
descriptors.stream()
.filter(d -> d.getType().endsWith("emergency_usage"))
.findFirst()
.orElseThrow();
assertThat(emergencyUsage.getMetricKind()).isEqualTo("GAUGE");
assertThat(emergencyUsage.getValueType()).isEqualTo("DOUBLE");
}
@Test
void testRecordStates_mapsStatusesToCorrectValues() throws IOException {
TldServiceState stateUp = createTldState("tld-up", "UP", "UP");
TldServiceState stateDown = createTldState("tld-down", "DOWN", "DOWN");
TldServiceState stateMaint = createTldState("tld-maint", "UP-INCONCLUSIVE", "DISABLED");
mosApiMetrics.recordStates(ImmutableList.of(stateUp, stateDown, stateMaint));
ArgumentCaptor<CreateTimeSeriesRequest> captor =
ArgumentCaptor.forClass(CreateTimeSeriesRequest.class);
verify(timeSeriesResource).create(eq("projects/" + PROJECT_ID), captor.capture());
List<TimeSeries> pushedSeries = captor.getValue().getTimeSeries();
// Verify TLD Status Mappings: 1 (UP), 0 (DOWN), 2 (UP-INCONCLUSIVE)
assertThat(getValueFor(pushedSeries, "tld-up", "tld_status")).isEqualTo(1);
assertThat(getValueFor(pushedSeries, "tld-down", "tld_status")).isEqualTo(0);
assertThat(getValueFor(pushedSeries, "tld-maint", "tld_status")).isEqualTo(2);
// Verify Service Status Mappings: UP -> 1, DOWN -> 0, DISABLED -> 2
assertThat(getValueFor(pushedSeries, "tld-up", "service_status")).isEqualTo(1);
assertThat(getValueFor(pushedSeries, "tld-down", "service_status")).isEqualTo(0);
assertThat(getValueFor(pushedSeries, "tld-maint", "service_status")).isEqualTo(2);
// 3. Verify Emergency Usage (DOUBLE)
assertThat(getValueFor(pushedSeries, "tld-up", "emergency_usage").doubleValue())
.isEqualTo(50.0);
assertThat(getValueFor(pushedSeries, "tld-down", "emergency_usage").doubleValue())
.isEqualTo(50.0);
assertThat(getValueFor(pushedSeries, "tld-maint", "emergency_usage").doubleValue())
.isEqualTo(50.0);
}
@Test
void testRecordStates_partitionsTimeSeries_atLimit() throws IOException {
ImmutableList<TldServiceState> largeBatch =
java.util.stream.IntStream.range(0, 70)
.mapToObj(i -> createTldState("tld-" + i, "UP", "UP"))
.collect(ImmutableList.toImmutableList());
mosApiMetrics.recordStates(largeBatch);
verify(timeSeriesResource, times(2))
.create(eq("projects/" + PROJECT_ID), any(CreateTimeSeriesRequest.class));
}
@Test
void testMetricStructure_containsExpectedLabelsAndResource() throws IOException {
TldServiceState state = createTldState("example.tld", "UP", "UP");
mosApiMetrics.recordStates(ImmutableList.of(state));
ArgumentCaptor<CreateTimeSeriesRequest> captor =
ArgumentCaptor.forClass(CreateTimeSeriesRequest.class);
verify(timeSeriesResource).create(anyString(), captor.capture());
TimeSeries ts = captor.getValue().getTimeSeries().get(0);
assertThat(ts.getMetric().getType()).startsWith("custom.googleapis.com/mosapi/");
assertThat(ts.getMetric().getLabels()).containsEntry("tld", "example.tld");
assertThat(ts.getResource().getType()).isEqualTo("global");
assertThat(ts.getResource().getLabels()).containsEntry("project_id", PROJECT_ID);
// Verify that the interval matches our fixed clock
assertThat(ts.getPoints().get(0).getInterval().getEndTime()).isEqualTo("2026-01-01T12:00:00Z");
}
/** Extracts the numeric value for a specific TLD and metric type from a list of TimeSeries. */
private Number getValueFor(List<TimeSeries> seriesList, String tld, String metricSuffix) {
String fullMetric = "custom.googleapis.com/mosapi/" + metricSuffix;
return seriesList.stream()
.filter(ts -> tld.equals(ts.getMetric().getLabels().get("tld")))
.filter(ts -> ts.getMetric().getType().equals(fullMetric))
.findFirst()
.map(
ts -> {
Double dVal = ts.getPoints().get(0).getValue().getDoubleValue();
if (dVal != null) {
return (Number) dVal;
}
// Fallback to Int64.
return (Number) ts.getPoints().get(0).getValue().getInt64Value();
})
.get();
}
@Test
void testRecordStates_skipsInitialization_ifLockNotAcquired() throws IOException {
when(lockHandler.executeWithLocks(any(Callable.class), any(), any(), any())).thenReturn(false);
TldServiceState state = createTldState("test.tld", "UP", "UP");
mosApiMetrics.recordStates(ImmutableList.of(state));
verify(metricDescriptorsResource, never()).create(anyString(), any());
}
/** Mocks a TldServiceState with a single service status. */
private TldServiceState createTldState(String tld, String tldStatus, String serviceStatus) {
ServiceStatus sStatus = mock(ServiceStatus.class);
when(sStatus.status()).thenReturn(serviceStatus);
when(sStatus.emergencyThreshold()).thenReturn(50.0);
TldServiceState state = mock(TldServiceState.class);
when(state.tld()).thenReturn(tld);
when(state.status()).thenReturn(tldStatus);
when(state.serviceStatuses()).thenReturn(ImmutableMap.of("dns", sStatus));
return state;
}
}

View File

@@ -169,4 +169,23 @@ class MosApiStateServiceTest {
&& states.stream()
.anyMatch(s -> s.tld().equals("tld1") && s.status().equals("Up"))));
}
@Test
void testTriggerMetrics_filtersOutInvalidContractStates() throws Exception {
// 1. Valid State
TldServiceState validState = new TldServiceState("tld1", 1L, "Up", ImmutableMap.of());
// 2. Invalid State (Status is NULL)
// We instantiate it directly to simulate a bad response object.
TldServiceState invalidState = new TldServiceState("tld2", 2L, null, ImmutableMap.of());
when(client.getTldServiceState("tld1")).thenReturn(validState);
when(client.getTldServiceState("tld2")).thenReturn(invalidState);
service.triggerMetricsForAllServiceStateSummaries();
// Verify: Only the valid state (tld1) is passed to recordStates
verify(metrics)
.recordStates(argThat(states -> states.size() == 1 && states.get(0).tld().equals("tld1")));
}
}

View File

@@ -298,9 +298,13 @@ have failed to comply with these terms.",
}
JsonFileBuilder addNameserver(String name, String handle) {
return addNameserver(Idn.toASCII(name), name, handle);
}
JsonFileBuilder addNameserver(String punycodeName, String unicodeName, String handle) {
return putNext(
"NAMESERVER_NAME_", Idn.toASCII(name),
"NAMESERVER_UNICODE_NAME_", name,
"NAMESERVER_NAME_", punycodeName,
"NAMESERVER_UNICODE_NAME_", unicodeName,
"NAMESERVER_HANDLE_", handle);
}

View File

@@ -17,6 +17,8 @@ package google.registry.rdap;
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.testing.FullFieldsTestEntityHelper.makeAndPersistHost;
import static google.registry.testing.FullFieldsTestEntityHelper.makePunycodedHost;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
import static google.registry.testing.GsonSubject.assertAboutJson;
import static org.mockito.Mockito.verify;
@@ -27,7 +29,6 @@ import google.registry.rdap.RdapMetrics.SearchType;
import google.registry.rdap.RdapMetrics.WildcardType;
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
import google.registry.request.Action;
import google.registry.testing.FullFieldsTestEntityHelper;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -43,20 +44,17 @@ class RdapNameserverActionTest extends RdapActionBaseTestCase<RdapNameserverActi
void beforeEach() {
// normal
createTld("lol");
FullFieldsTestEntityHelper.makeAndPersistHost(
"ns1.cat.lol", "1.2.3.4", clock.nowUtc().minusYears(1));
makeAndPersistHost("ns1.cat.lol", "1.2.3.4", clock.nowUtc().minusYears(1));
// idn
createTld("xn--q9jyb4c");
FullFieldsTestEntityHelper.makeAndPersistHost(
makeAndPersistHost(
"ns1.cat.xn--q9jyb4c", "bad:f00d:cafe:0:0:0:15:beef", clock.nowUtc().minusYears(1));
// multilevel
createTld("1.tld");
FullFieldsTestEntityHelper.makeAndPersistHost(
"ns1.domain.1.tld", "5.6.7.8", clock.nowUtc().minusYears(1));
makeAndPersistHost("ns1.domain.1.tld", "5.6.7.8", clock.nowUtc().minusYears(1));
// deleted
persistResource(
FullFieldsTestEntityHelper.makeAndPersistHost(
"nsdeleted.cat.lol", "1.2.3.4", clock.nowUtc().minusYears(1))
makeAndPersistHost("nsdeleted.cat.lol", "1.2.3.4", clock.nowUtc().minusYears(1))
.asBuilder()
.setDeletionTime(clock.nowUtc().minusMonths(1))
.build());
@@ -64,8 +62,7 @@ class RdapNameserverActionTest extends RdapActionBaseTestCase<RdapNameserverActi
persistResource(
makeRegistrar("otherregistrar", "Yes Virginia <script>", Registrar.State.ACTIVE, 102L));
// external
FullFieldsTestEntityHelper.makeAndPersistHost(
"ns1.domain.external", "9.10.11.12", clock.nowUtc().minusYears(1));
makeAndPersistHost("ns1.domain.external", "9.10.11.12", clock.nowUtc().minusYears(1));
}
@Test
@@ -80,6 +77,14 @@ class RdapNameserverActionTest extends RdapActionBaseTestCase<RdapNameserverActi
assertThat(response.getStatus()).isEqualTo(400);
}
@Test
void testInvalidNameserver_domainWithHyphenOn3And4_returns400() {
assertAboutJson()
.that(generateActualJson("ns1.zz--main-2166.lol"))
.isEqualTo(generateExpectedJsonError("Not a valid nameserver", 400));
assertThat(response.getStatus()).isEqualTo(400);
}
@Test
void testUnknownNameserver_returns404() {
assertAboutJson()
@@ -101,6 +106,21 @@ class RdapNameserverActionTest extends RdapActionBaseTestCase<RdapNameserverActi
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
void testNameserver_tldTithHyphenOn3And4_works() {
createTld("zz--main-2166");
persistResource(makePunycodedHost("ns1.cat.zz--main-2166", "1.2.3.4", null, "TheRegistrar"));
assertAboutJson()
.that(generateActualJson("ns1.cat.zz--main-2166"))
.isEqualTo(
addPermanentBoilerplateNotices(
jsonFileBuilder()
.addNameserver("ns1.cat.zz--main-2166", "ns1.cat.zz--main-2166", "F-ROID")
.putAll("ADDRESSTYPE", "v4", "ADDRESS", "1.2.3.4", "STATUS", "active")
.load("rdap_host.json")));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
void testTrailingDot_getsIgnored() {
assertAboutJson()

View File

@@ -28,17 +28,12 @@ import static google.registry.xjc.rgp.XjcRgpStatusValueType.TRANSFER_PERIOD;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.money.CurrencyUnit.USD;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
import google.registry.model.billing.BillingBase.Flag;
import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingRecurrence;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactAddress;
import google.registry.model.contact.ContactPhoneNumber;
import google.registry.model.contact.PostalInfo;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
@@ -73,7 +68,6 @@ import google.registry.xjc.rgp.XjcRgpStatusType;
import google.registry.xjc.secdns.XjcSecdnsDsDataType;
import google.registry.xml.XmlException;
import java.io.ByteArrayOutputStream;
import java.util.Optional;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
@@ -109,7 +103,7 @@ public class DomainToXjcConverterTest {
assertThat(
bean.getContacts().stream()
.map(input -> String.format("%s %s", input.getType().toString(), input.getValue())))
.containsExactly("ADMIN 5372808-IRL", "TECH 5372808-TRL");
.containsExactly("ADMIN contact1234", "TECH contact1234");
assertThat(bean.getCrDate()).isEqualTo(DateTime.parse("1900-01-01T00:00:00Z"));
@@ -138,7 +132,7 @@ public class DomainToXjcConverterTest {
// name used to generate the IDN variant.
// TODO(b/26125498): bean.getOriginalName()
assertThat(bean.getRegistrant()).isEqualTo("5372808-ERL");
assertThat(bean.getRegistrant()).isEqualTo("contact1234");
// o Zero or more OPTIONAL <rgpStatus> element to represent
// "pendingDelete" sub-statuses, including "redemptionPeriod",
@@ -264,26 +258,6 @@ public class DomainToXjcConverterTest {
domain
.asBuilder()
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("secret")))
.setContacts(
ImmutableSet.of(
DesignatedContact.create(
DesignatedContact.Type.ADMIN,
makeContact(
clock,
"10-Q9JYB4C",
"5372808-IRL",
"be that word our sign in parting",
"BOFH@cat.みんな")
.createVKey()),
DesignatedContact.create(
DesignatedContact.Type.TECH,
makeContact(
clock,
"11-Q9JYB4C",
"5372808-TRL",
"bird or fiend!? i shrieked upstarting",
"bog@cat.みんな")
.createVKey())))
.setCreationRegistrarId("TheRegistrar")
.setCreationTimeForTest(DateTime.parse("1900-01-01T00:00:00Z"))
.setPersistedCurrentSponsorRegistrarId("TheRegistrar")
@@ -298,15 +272,6 @@ public class DomainToXjcConverterTest {
makeHost(clock, "3-Q9JYB4C", "bird.or.devil.みんな", "1.2.3.4").createVKey(),
makeHost(clock, "4-Q9JYB4C", "ns2.cat.みんな", "bad:f00d:cafe::15:beef")
.createVKey()))
.setRegistrant(
Optional.of(
makeContact(
clock,
"12-Q9JYB4C",
"5372808-ERL",
"(◕‿◕) nevermore",
"prophet@evil.みんな")
.createVKey()))
.setRegistrationExpirationTime(DateTime.parse("1930-01-01T00:00:00Z"))
.setGracePeriods(
ImmutableSet.of(
@@ -404,37 +369,6 @@ public class DomainToXjcConverterTest {
return persistResource(domain);
}
private static Contact makeContact(
FakeClock clock, String repoId, String id, String name, String email) {
clock.advanceOneMilli();
return persistEppResource(
new Contact.Builder()
.setContactId(id)
.setEmailAddress(email)
.setPersistedCurrentSponsorRegistrarId("TheRegistrar")
.setCreationRegistrarId("TheRegistrar")
.setCreationTimeForTest(END_OF_TIME)
.setInternationalizedPostalInfo(
new PostalInfo.Builder()
.setType(PostalInfo.Type.INTERNATIONALIZED)
.setName(name)
.setOrg("SINNERS INCORPORATED")
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("123 Example Boulevard"))
.setCity("KOKOMO")
.setState("BM")
.setZip("31337")
.setCountryCode("US")
.build())
.build())
.setRepoId(repoId)
.setVoiceNumber(
new ContactPhoneNumber.Builder().setPhoneNumber("+1.2126660420").build())
.setFaxNumber(new ContactPhoneNumber.Builder().setPhoneNumber("+1.2126660421").build())
.build());
}
private static Host makeHost(FakeClock clock, String repoId, String fqhn, String ip) {
clock.advanceOneMilli();
return persistEppResource(

View File

@@ -25,7 +25,6 @@ import static com.google.common.collect.MoreCollectors.onlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.config.RegistryConfig.getContactAndHostRoidSuffix;
import static google.registry.config.RegistryConfig.getContactAutomaticTransferLength;
import static google.registry.model.EppResourceUtils.createDomainRepoId;
import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
@@ -76,7 +75,6 @@ import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactHistory;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.Domain;
@@ -536,53 +534,6 @@ public final class DatabaseHelper {
.build();
}
public static Contact persistContactWithPendingTransfer(
Contact contact, DateTime requestTime, DateTime expirationTime, DateTime now) {
ContactHistory historyEntryContactTransfer =
persistResource(
new ContactHistory.Builder()
.setType(HistoryEntry.Type.CONTACT_TRANSFER_REQUEST)
.setContact(persistResource(contact))
.setModificationTime(now)
.setRegistrarId(contact.getCurrentSponsorRegistrarId())
.build());
return persistResource(
contact
.asBuilder()
.setPersistedCurrentSponsorRegistrarId("TheRegistrar")
.addStatusValue(StatusValue.PENDING_TRANSFER)
.setTransferData(
createContactTransferDataBuilder(requestTime, expirationTime)
.setPendingTransferExpirationTime(now.plus(getContactAutomaticTransferLength()))
.setServerApproveEntities(
historyEntryContactTransfer.getRepoId(),
historyEntryContactTransfer.getRevisionId(),
ImmutableSet.of(
// Pretend it's 3 days since the request
persistResource(
createPollMessageForImplicitTransfer(
contact,
historyEntryContactTransfer,
"NewRegistrar",
requestTime,
expirationTime,
null))
.createVKey(),
persistResource(
createPollMessageForImplicitTransfer(
contact,
historyEntryContactTransfer,
"TheRegistrar",
requestTime,
expirationTime,
null))
.createVKey()))
.setTransferRequestTrid(
Trid.create("transferClient-trid", "transferServer-trid"))
.build())
.build());
}
public static Domain persistDomainWithDependentResources(
String label,
String tld,

View File

@@ -135,10 +135,15 @@ public final class FullFieldsTestEntityHelper {
public static Host makeHost(
String fqhn, @Nullable String ip1, @Nullable String ip2, String registrarClientId) {
return makePunycodedHost(Idn.toASCII(fqhn), ip1, ip2, registrarClientId);
}
public static Host makePunycodedHost(
String fqhn, @Nullable String ip1, @Nullable String ip2, String registrarClientId) {
Host.Builder builder =
new Host.Builder()
.setRepoId(generateNewContactHostRoid())
.setHostName(Idn.toASCII(fqhn))
.setHostName(fqhn)
.setCreationTimeForTest(DateTime.parse("2000-10-08T00:45:00Z"))
.setPersistedCurrentSponsorRegistrarId(registrarClientId);
if ((ip1 != null) || (ip2 != null)) {

View File

@@ -31,35 +31,33 @@ class ValidateEscrowDepositCommandTest extends CommandTestCase<ValidateEscrowDep
assertThat(getStdoutAsString())
.isEqualTo(
"""
ID: 20101017001
Previous ID: 20101010001
Type: FULL
Watermark: 2010-10-17T00:00:00.000Z
RDE Version: 1.0
ID: 20101017001
Previous ID: 20101010001
Type: FULL
Watermark: 2010-10-17T00:00:00.000Z
RDE Version: 1.0
RDE Object URIs:
- urn:ietf:params:xml:ns:rdeContact-1.0
- urn:ietf:params:xml:ns:rdeDomain-1.0
- urn:ietf:params:xml:ns:rdeEppParams-1.0
- urn:ietf:params:xml:ns:rdeHeader-1.0
- urn:ietf:params:xml:ns:rdeHost-1.0
- urn:ietf:params:xml:ns:rdeIDN-1.0
- urn:ietf:params:xml:ns:rdeNNDN-1.0
- urn:ietf:params:xml:ns:rdeRegistrar-1.0
RDE Object URIs:
- urn:ietf:params:xml:ns:rdeDomain-1.0
- urn:ietf:params:xml:ns:rdeEppParams-1.0
- urn:ietf:params:xml:ns:rdeHeader-1.0
- urn:ietf:params:xml:ns:rdeHost-1.0
- urn:ietf:params:xml:ns:rdeIDN-1.0
- urn:ietf:params:xml:ns:rdeNNDN-1.0
- urn:ietf:params:xml:ns:rdeRegistrar-1.0
Contents:
- XjcRdeContact: 1 entry
- XjcRdeDomain: 2 entries
- XjcRdeEppParams: 1 entry
- XjcRdeHeader: 1 entry
- XjcRdeHost: 2 entries
- XjcRdeIdn: 1 entry
- XjcRdeNndn: 1 entry
- XjcRdePolicy: 1 entry
- XjcRdeRegistrar: 1 entry
Contents:
- XjcRdeDomain: 2 entries
- XjcRdeEppParams: 1 entry
- XjcRdeHeader: 1 entry
- XjcRdeHost: 2 entries
- XjcRdeIdn: 1 entry
- XjcRdeNndn: 1 entry
- XjcRdePolicy: 1 entry
- XjcRdeRegistrar: 1 entry
RDE deposit is XML schema valid
""");
RDE deposit is XML schema valid
""");
}
@Test
@@ -69,46 +67,44 @@ class ValidateEscrowDepositCommandTest extends CommandTestCase<ValidateEscrowDep
assertThat(getStdoutAsString())
.isEqualTo(
"""
ID: 20101017001
Previous ID: 20101010001
Type: FULL
Watermark: 2010-10-17T00:00:00.000Z
RDE Version: 1.0
ID: 20101017001
Previous ID: 20101010001
Type: FULL
Watermark: 2010-10-17T00:00:00.000Z
RDE Version: 1.0
RDE Object URIs:
- urn:ietf:params:xml:ns:rdeContact-1.0
- urn:ietf:params:xml:ns:rdeDomain-1.0
- urn:ietf:params:xml:ns:rdeEppParams-1.0
- urn:ietf:params:xml:ns:rdeHeader-1.0
- urn:ietf:params:xml:ns:rdeHost-1.0
- urn:ietf:params:xml:ns:rdeIDN-1.0
- urn:ietf:params:xml:ns:rdeNNDN-1.0
- urn:ietf:params:xml:ns:rdeRegistrar-1.0
RDE Object URIs:
- urn:ietf:params:xml:ns:rdeDomain-1.0
- urn:ietf:params:xml:ns:rdeEppParams-1.0
- urn:ietf:params:xml:ns:rdeHeader-1.0
- urn:ietf:params:xml:ns:rdeHost-1.0
- urn:ietf:params:xml:ns:rdeIDN-1.0
- urn:ietf:params:xml:ns:rdeNNDN-1.0
- urn:ietf:params:xml:ns:rdeRegistrar-1.0
Contents:
- XjcRdeContact: 1 entry
- XjcRdeDomain: 2 entries
- XjcRdeEppParams: 1 entry
- XjcRdeHeader: 1 entry
- XjcRdeHost: 2 entries
- XjcRdeIdn: 1 entry
- XjcRdeNndn: 1 entry
- XjcRdePolicy: 1 entry
- XjcRdeRegistrar: 1 entry
Contents:
- XjcRdeDomain: 2 entries
- XjcRdeEppParams: 1 entry
- XjcRdeHeader: 1 entry
- XjcRdeHost: 2 entries
- XjcRdeIdn: 1 entry
- XjcRdeNndn: 1 entry
- XjcRdePolicy: 1 entry
- XjcRdeRegistrar: 1 entry
Bad host refs: ns1.LAFFO.com
RDE deposit is XML schema valid but has bad references
""");
Bad host refs: ns1.LAFFO.com
RDE deposit is XML schema valid but has bad references
""");
}
@Test
void testRun_badXml() throws Exception {
void testRun_badXml_truncatedFile() throws Exception {
String file = writeToTmpFile(RdeTestData.loadFile("deposit_full.xml").substring(0, 2000));
XmlException thrown = assertThrows(XmlException.class, () -> runCommand("--input=" + file));
assertThat(thrown)
.hasMessageThat()
.contains(
"Syntax error at line 46, column 38: "
"Syntax error at line 49, column 18: "
+ "XML document structures must start and end within the same entity.");
}
}

View File

@@ -45,7 +45,7 @@ class XjcObjectTest {
String xml = out.toString(UTF_8.toString());
Pattern pat = Pattern.compile("^<\\?xml version=\"1\\.0\" encoding=\"UTF[-_]?8\"");
assertWithMessage("bad xml declaration: " + xml).that(pat.matcher(xml).find()).isTrue();
assertWithMessage("encode/decode didn't work: " + xml).that(xml).contains("Jane Doe");
assertWithMessage("encode/decode didn't work: " + xml).that(xml).contains("jdoe@example.test");
}
@Test
@@ -56,7 +56,7 @@ class XjcObjectTest {
String xml = out.toString(UTF_16.toString());
Pattern pat = Pattern.compile("^<\\?xml version=\"1\\.0\" encoding=\"UTF[-_]?16\"");
assertWithMessage(xml).that(pat.matcher(xml).find()).isTrue();
assertWithMessage("encode/decode didn't work: " + xml).that(xml).contains("Jane Doe");
assertWithMessage("encode/decode didn't work: " + xml).that(xml).contains("jdoe@example.test");
}
@Test

View File

@@ -3,7 +3,6 @@
<rde:watermark>2000-01-01T00:00:00Z</rde:watermark>
<rde:rdeMenu>
<rde:version>1.0</rde:version>
<rde:objURI>urn:ietf:params:xml:ns:rdeContact-1.0</rde:objURI>
<rde:objURI>urn:ietf:params:xml:ns:rdeDomain-1.0</rde:objURI>
<rde:objURI>urn:ietf:params:xml:ns:rdeHeader-1.0</rde:objURI>
<rde:objURI>urn:ietf:params:xml:ns:rdeHost-1.0</rde:objURI>
@@ -13,7 +12,6 @@
<rde:contents>
<rdeDomain:domain/>
<rdeRegistrar:registrar/>
<rdeContact:contact/>
<rdeHost:host/>
<rdeIDN:idnTableRef id="extended_latin">
@@ -38,7 +36,6 @@
<rdeHeader:header>
<rdeHeader:tld>soy</rdeHeader:tld>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeContact-1.0">1</rdeHeader:count>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeDomain-1.0">1</rdeHeader:count>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeHost-1.0">1</rdeHeader:count>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeRegistrar-1.0">1</rdeHeader:count>

View File

@@ -10,7 +10,6 @@
<rdeReport:watermark>2000-01-01T00:00:00Z</rdeReport:watermark>
<rdeHeader:header>
<rdeHeader:tld>soy</rdeHeader:tld>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeContact-1.0">1</rdeHeader:count>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeDomain-1.0">1</rdeHeader:count>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeHost-1.0">1</rdeHeader:count>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeRegistrar-1.0">1</rdeHeader:count>

View File

@@ -0,0 +1,92 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:chkData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:cd>
<domain:name avail="true">example1.tld</domain:name>
</domain:cd>
<domain:cd>
<domain:name avail="false">example2.example</domain:name>
<domain:reason>Alloc token invalid for domain</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="false">reserved.tld</domain:name>
<domain:reason>Alloc token invalid for domain</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="false">rich.example</domain:name>
<domain:reason>Alloc token invalid for domain</domain:reason>
</domain:cd>
</domain:chkData>
</resData>
<extension>
<fee:chkData xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:currency>USD</fee:currency>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">0.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example2.example</fee:objID>
<fee:class>token-not-supported</fee:class>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>reserved.tld</fee:objID>
<fee:class>token-not-supported</fee:class>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>rich.example</fee:objID>
<fee:class>token-not-supported</fee:class>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:class>premium</fee:class>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example2.example</fee:objID>
<fee:class>token-not-supported</fee:class>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>reserved.tld</fee:objID>
<fee:class>token-not-supported</fee:class>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>rich.example</fee:objID>
<fee:class>token-not-supported</fee:class>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View File

@@ -0,0 +1,91 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:chkData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:cd>
<domain:name avail="true">example1.tld</domain:name>
</domain:cd>
<domain:cd>
<domain:name avail="true">example2.example</domain:name>
</domain:cd>
<domain:cd>
<domain:name avail="false">reserved.tld</domain:name>
<domain:reason>Reserved</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="true">rich.example</domain:name>
</domain:cd>
</domain:chkData>
</resData>
<extension>
<fee:chkData xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:currency>USD</fee:currency>
<fee:cd>
<fee:objID>example2.example</fee:objID>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">6.50</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">6.50</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>reserved.tld</fee:objID>
<fee:class>reserved</fee:class>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>rich.example</fee:objID>
<fee:class>premium</fee:class>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">100.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example2.example</fee:objID>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>reserved.tld</fee:objID>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>rich.example</fee:objID>
<fee:class>premium</fee:class>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">100.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View File

@@ -0,0 +1,63 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:chkData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:cd>
<domain:name avail="false">example1.tld</domain:name>
<domain:reason>Alloc token invalid for domain</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="false">example2.example</domain:name>
<domain:reason>Alloc token invalid for domain</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="false">reserved.tld</domain:name>
<domain:reason>Alloc token invalid for domain</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="true">specificuse.tld</domain:name>
</domain:cd>
</domain:chkData>
</resData>
<extension>
<fee:chkData xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:currency>USD</fee:currency>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:class>token-not-supported</fee:class>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example2.example</fee:objID>
<fee:class>token-not-supported</fee:class>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>reserved.tld</fee:objID>
<fee:class>token-not-supported</fee:class>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>specificuse.tld</fee:objID>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View File

@@ -0,0 +1,26 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example1.tld</domain:name>
<domain:name>example2.example</domain:name>
<domain:name>reserved.tld</domain:name>
<domain:name>specificuse.tld</domain:name>
</domain:check>
</check>
<extension>
<allocationToken:allocationToken
xmlns:allocationToken=
"urn:ietf:params:xml:ns:allocationToken-1.0">
abc123
</allocationToken:allocationToken>
<fee:check
xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:currency>USD</fee:currency>
<fee:command name="create" />
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View File

@@ -0,0 +1,27 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example1.tld</domain:name>
<domain:name>example2.example</domain:name>
<domain:name>reserved.tld</domain:name>
<domain:name>rich.example</domain:name>
</domain:check>
</check>
<extension>
<allocationToken:allocationToken
xmlns:allocationToken=
"urn:ietf:params:xml:ns:allocationToken-1.0">
abc123
</allocationToken:allocationToken>
<fee:check
xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:currency>USD</fee:currency>
<fee:command name="create" />
<fee:command name="renew" />
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View File

@@ -17,4 +17,4 @@
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
</epp>

View File

@@ -0,0 +1,47 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:chkData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:cd>
<domain:name avail="true">%DOMAIN%</domain:name>
</domain:cd>
</domain:chkData>
</resData>
<extension>
<fee:chkData xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:currency>USD</fee:currency>
<fee:cd>
<fee:objID>%DOMAIN%</fee:objID>
%FEE_CLASS%
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">%COST_1YR%</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>%DOMAIN%</fee:objID>
%FEE_CLASS%
<fee:command name="create">
<fee:period unit="y">2</fee:period>
<fee:fee description="create">%COST_2YR%</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>%DOMAIN%</fee:objID>
%FEE_CLASS%
<fee:command name="create">
<fee:period unit="y">5</fee:period>
<fee:fee description="create">%COST_5YR%</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

Some files were not shown because too many files have changed in this diff Show More