1
0
mirror of https://github.com/google/nomulus synced 2026-07-06 16:16:38 +00:00

Compare commits

...

17 Commits

Author SHA1 Message Date
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
gbrodman 40184689ca Allow for a currency unit in fee:check responses (#2922)
This is / will be required in https://datatracker.ietf.org/doc/rfc8748/.
I split this out from the rest of the fee-extension testing so that it
can be easily visible.
2026-01-07 21:12:20 +00:00
Nilay Shah 826ad85d20 Add endpoint to trigger MoSAPI metrics export (#2923)
This commit introduces a new backend endpoint at `/_dr/task/triggerMosApiServiceState` that initiates the process of fetching the latest service states for all TLDs from the MoSAPI endpoint and exporting them as metrics to Cloud Monitoring.

  The key changes include:
   - A new `TriggerServiceStateAction` class that handles the GET request to the new endpoint.
     - Logic within `MosApiStateService` to concurrently fetch states for all configured TLDs.
     - A new `MosApiMetrics` class (currently a placeholder) responsible for sending the collected states to the monitoring service.
     - Unit tests for the new action and the updated service logic.

This endpoint will be called periodically to ensure that the MosApi service health metrics are kept up-to-date.
2026-01-07 19:13:19 +00:00
gbrodman 2b47bc9b0a Move fee class from extension to item (#2924)
this is coming from the schema https://datatracker.ietf.org/doc/rfc8748/
section 6.1. The class, that we use for "premium" notes, moved from the
command to the object itself.
2026-01-06 19:00:19 +00:00
gbrodman 9555dca8c6 Don't allow loopback IP addresses for hosts (#2920)
I don't know where in the spec these are explicitly disallowed, but it
seems like good practice and we'll fail the RST tests if we don't
disallow them.
2026-01-05 21:29:15 +00:00
Ben McIlwain 49484c06d3 Filter out registrars of type OT&E from RDE escrow deposits (#2921)
The RDE XML schema (which is verified by ICANN's RST) requires the presence of a
numeric IANA identifier, which is always null for OT&E registrars. This change
synchronizes the three types of registrars that must have a null IANA identifier
(see
https://cs.opensource.google/nomulus/nomulus/+/master:core/src/main/java/google/registry/model/registrar/Registrar.java;l=109-142;drc=b1266c95e8d9f8206415d2821929d4161869b699
) with the registrars that are excluded from the RDE deposit. Note that there
are no registrars of type OT&E in prod and I can't think of a reason they would
need to be included in escrow deposits on sandbox.
2026-01-05 21:20:11 +00:00
Nilay Shah 81d222e7d6 Add GetServiceState action for MoSAPI service monitoring (#2906)
* Add GetServiceState action for MoSAPI service monitoring

Implements the `/api/mosapi/getServiceState` endpoint to retrieve service health summaries for TLDs from the MoSAPI system.

- Introduces `GetServiceStateAction` to fetch TLD service status.
- Implements `MosApiStateService` to transform raw MoSAPI responses into a curated `ServiceStateSummary`.
- Uses concurrent processing with a fixed thread pool to fetch states for all configured TLDs efficiently while respecting MoSAPI rate limits.

junit test added

* Refactor MoSAPI models to records and address review nits

- Convert model classes to Java records for conciseness and immutability.
- Update unit tests to use Java text blocks for improved JSON readability.
- Simplify service and action layers by removing redundant logic and logging.
- Fix configuration nits regarding primitive types and comment formatting.

* Consolidate MoSAPI models and enhance null-safety

- Moves model records into a single MosApiModels.java file.
- Switches to ImmutableList/ImmutableMap with non-null defaults in constructors.
- Removes redundant pass-through methods in MosApiStateService.
- Updates tests to use Java Text Blocks and non-null collection assertions.

* Improve MoSAPI client error handling and clean up data models

Refactors the MoSAPI monitoring client to be more robust against
infrastructure failures

* Refactor: use nullToEmptyImmutableCopy() for MoSAPI models

Standardize null-handling in model classes by using the Nomulus
`nullToEmptyImmutableCopy()` utility. This ensures consistent API
responses with empty lists instead of omitted fields.
2026-01-05 15:44:01 +00:00
Weimin Yu 7e9d4c27d1 Use downloaded Gradle distribution on Cloud Build (#2918)
This way we get around the http url and no longer needs public access on
the GCS bucket.
2025-12-30 21:08:04 +00:00
Weimin Yu f9c22ff1c5 Add RST support in Sandbox (#2917)
* Add RST support in Sandbox

Added RST test label files as resources.

Added a RstTmchUtils class that loads appropriate labels according to
TLD pattern.

Temporarily changed label fetching in production to include the TLD
string, so that the new class may know which set of labels to use.

* Addressing comments

* Addressing comments
2025-12-30 20:59:28 +00:00
gbrodman 2562d582f3 Add more strict hostname validation on host:check flows (#2915)
We do most of these on host create already so we should also do them on
host checks. The only added change is the character validation (our
existing hostnames all match these).
2025-12-30 16:41:56 +00:00
Ben McIlwain 6f0bc1ded9 Add Augmented Latin IDN table to IDN enums (#2914)
This was added in https://github.com/google/nomulus/pull/2884 , but now as of
this PR it can actually be configured and used on a TLD.
2025-12-27 00:57:24 +00:00
gbrodman db9fc3271d Change EPP errors 2306->2005 for some structural issues (#2911)
2306 signifies something that is syntactically valid but semantically
invalid (like if someone tried to register a .com domain). These errors
are for domain syntax that could never be valid, thus we should throw a
syntax exception instead of a policy exception.
2025-12-26 16:08:04 +00:00
Ben McIlwain 84491fde70 Don't allow underscores in TLD ROID suffixes (#2913)
Per ICANN it's a disallowed character.
2025-12-26 16:01:28 +00:00
125 changed files with 3926 additions and 403 deletions
@@ -184,10 +184,10 @@ public class RdePipeline implements Serializable {
private final CloudTasksUtils cloudTasksUtils;
private final RdeMarshaller marshaller;
// Registrars to be excluded from data escrow. Not including the sandbox-only OTE type so that
// if sneaks into production we would get an extra signal.
// Registrars to be excluded from data escrow (i.e. all registrar types that have a null IANA
// identifier and thus would not be valid according to the RDE schema).
private static final ImmutableSet<Type> IGNORED_REGISTRAR_TYPES =
Sets.immutableEnumSet(Registrar.Type.MONITORING, Registrar.Type.TEST);
Sets.immutableEnumSet(Registrar.Type.MONITORING, Registrar.Type.OTE, Registrar.Type.TEST);
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -1462,6 +1462,12 @@ public final class RegistryConfig {
return ImmutableSet.copyOf(config.mosapi.services);
}
@Provides
@Config("mosapiTldThreadCnt")
public static int provideMosapiTldThreads(RegistryConfigSettings config) {
return config.mosapi.tldThreadCnt;
}
private static String formatComments(String text) {
return Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(text).stream()
.map(s -> "# " + s)
@@ -272,5 +272,6 @@ public class RegistryConfigSettings {
public String entityType;
public List<String> tlds;
public List<String> services;
public int tldThreadCnt;
}
}
@@ -642,4 +642,8 @@ mosapi:
- "epp"
- "dnssec"
# Provides a fixed thread pool for parallel TLD processing.
# @see <a href="https://www.icann.org/mosapi-specification.pdf">
# ICANN MoSAPI Specification, Section 12.3</a>
tldThreadCnt: 4
@@ -195,14 +195,31 @@ 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)
public static <T> void checkSameValuesNotAddedAndRemoved(
ImmutableSet<T> fieldsToAdd, ImmutableSet<T> fieldsToRemove)
throws AddRemoveSameValueException {
if (!intersection(fieldsToAdd, fieldsToRemove).isEmpty()) {
throw new AddRemoveSameValueException();
}
}
/** Check that we aren't adding a value that is already present. */
public static <T> void checkExistingValueNotAdded(
ImmutableSet<T> fieldsToAdd, ImmutableSet<T> existingFields)
throws AddExistingValueException {
if (!intersection(fieldsToAdd, existingFields).isEmpty()) {
throw new AddExistingValueException();
}
}
public static <T> void checkNonexistentValueNotRemoved(
ImmutableSet<T> fieldsToRemove, ImmutableSet<T> existingFields)
throws RemoveNonexistentValueException {
if (intersection(fieldsToRemove, existingFields).size() != fieldsToRemove.size()) {
throw new RemoveNonexistentValueException();
}
}
/** Check that all {@link StatusValue} objects in a set are client-settable. */
public static void verifyAllStatusesAreClientSettable(Set<StatusValue> statusValues)
throws StatusNotClientSettableException {
@@ -266,6 +283,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) {
@@ -88,6 +88,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
/**
@@ -298,11 +299,13 @@ public final class DomainCheckFlow implements TransactionalFlow {
boolean shouldUseTieredPricingPromotion =
RegistryConfig.getTieredPricingPromotionRegistrarIds().contains(registrarId);
ImmutableSet.Builder<CurrencyUnit> currenciesBuilder = new ImmutableSet.Builder<>();
for (FeeCheckCommandExtensionItem feeCheckItem : feeCheck.getItems()) {
for (String domainName : getDomainNamesToCheckForFee(feeCheckItem, domainNames.keySet())) {
FeeCheckResponseExtensionItem.Builder<?> builder = feeCheckItem.createResponseBuilder();
Optional<Domain> domain = Optional.ofNullable(domainObjs.get(domainName));
Tld tld = Tld.get(domainNames.get(domainName).parent().toString());
currenciesBuilder.add(tld.getCurrency());
Optional<AllocationToken> token;
try {
// The precise token to use for this fee request may vary based on the domain or even the
@@ -385,7 +388,8 @@ public final class DomainCheckFlow implements TransactionalFlow {
responseItems.add(builder.setDomainNameIfSupported(domainName).build());
}
}
return ImmutableList.of(feeCheck.createResponse(responseItems.build()));
return ImmutableList.of(
feeCheck.createResponse(responseItems.build(), currenciesBuilder.build()));
}
/**
@@ -108,7 +108,8 @@ public final class DomainClaimsCheckFlow implements TransactionalFlow {
verifyClaimsPeriodNotEnded(tld, now);
}
}
Optional<String> claimKey = ClaimsListDao.get().getClaimKey(parsedDomain.parts().get(0));
Optional<String> claimKey =
ClaimsListDao.get(tldStr).getClaimKey(parsedDomain.parts().get(0));
launchChecksBuilder.add(
LaunchCheck.create(
LaunchCheckName.create(claimKey.isPresent(), domainName), claimKey.orElse(null)));
@@ -280,7 +280,7 @@ public final class DomainCreateFlow implements MutatingFlow {
checkAllowedAccessToTld(registrarId, tld.getTldStr());
checkHasBillingAccount(registrarId, tld.getTldStr());
boolean isValidReservedCreate = isValidReservedCreate(domainName, allocationToken);
ClaimsList claimsList = ClaimsListDao.get();
ClaimsList claimsList = ClaimsListDao.get(tld.getTldStr());
verifyIsGaOrSpecialCase(
tld,
claimsList,
@@ -312,7 +312,8 @@ public final class DomainCreateFlow implements MutatingFlow {
// at this point so that we can verify it before the "after validation" extension point.
signedMarkId =
tmchUtils
.verifySignedMarks(launchCreate.get().getSignedMarks(), domainLabel, now)
.verifySignedMarks(
tld.getTldStr(), launchCreate.get().getSignedMarks(), domainLabel, now)
.getId();
}
verifyNotBlockedByBsa(domainName, tld, now, allocationToken);
@@ -55,7 +55,7 @@ public final class DomainFlowTmchUtils {
}
public SignedMark verifySignedMarks(
ImmutableList<AbstractSignedMark> signedMarks, String domainLabel, DateTime now)
String tld, ImmutableList<AbstractSignedMark> signedMarks, String domainLabel, DateTime now)
throws EppException {
if (signedMarks.size() > 1) {
throw new TooManySignedMarksException();
@@ -64,7 +64,7 @@ public final class DomainFlowTmchUtils {
throw new SignedMarksMustBeEncodedException();
}
SignedMark signedMark =
verifyEncodedSignedMark((EncodedSignedMark) signedMarks.get(0), now);
verifyEncodedSignedMark(tld, (EncodedSignedMark) signedMarks.get(0), now);
return verifySignedMarkValidForDomainLabel(signedMark, domainLabel);
}
@@ -76,8 +76,9 @@ public final class DomainFlowTmchUtils {
return signedMark;
}
public SignedMark verifyEncodedSignedMark(EncodedSignedMark encodedSignedMark, DateTime now)
throws EppException {
// TODO(b/412715713): remove the tld parameter when RST completes.
public SignedMark verifyEncodedSignedMark(
String tld, EncodedSignedMark encodedSignedMark, DateTime now) throws EppException {
if (!encodedSignedMark.getEncoding().equals("base64")) {
throw new Base64RequiredForEncodedSignedMarksException();
}
@@ -95,7 +96,7 @@ public final class DomainFlowTmchUtils {
throw new SignedMarkParsingErrorException();
}
if (SignedMarkRevocationList.get().isSmdRevoked(signedMark.getId(), now)) {
if (SignedMarkRevocationList.get(tld).isSmdRevoked(signedMark.getId(), now)) {
throw new SignedMarkRevokedErrorException();
}
@@ -218,7 +218,7 @@ public class DomainFlowUtils {
return domainName;
}
private static void validateFirstLabel(String firstLabel) throws EppException {
public static void validateFirstLabel(String firstLabel) throws EppException {
if (firstLabel.length() > MAX_LABEL_SIZE) {
throw new DomainLabelTooLongException();
}
@@ -1240,49 +1240,49 @@ public class DomainFlowUtils {
}
/** Domain names can only contain a-z, 0-9, '.' and '-'. */
static class BadDomainNameCharacterException extends ParameterValuePolicyErrorException {
static class BadDomainNameCharacterException extends ParameterValueSyntaxErrorException {
public BadDomainNameCharacterException() {
super("Domain names can only contain a-z, 0-9, '.' and '-'");
}
}
/** Non-IDN domain names cannot contain hyphens in the third or fourth position. */
static class DashesInThirdAndFourthException extends ParameterValuePolicyErrorException {
static class DashesInThirdAndFourthException extends ParameterValueSyntaxErrorException {
public DashesInThirdAndFourthException() {
super("Non-IDN domain names cannot contain dashes in the third or fourth position");
}
}
/** Domain labels cannot begin with a dash. */
static class LeadingDashException extends ParameterValuePolicyErrorException {
static class LeadingDashException extends ParameterValueSyntaxErrorException {
public LeadingDashException() {
super("Domain labels cannot begin with a dash");
}
}
/** Domain labels cannot end with a dash. */
static class TrailingDashException extends ParameterValuePolicyErrorException {
static class TrailingDashException extends ParameterValueSyntaxErrorException {
public TrailingDashException() {
super("Domain labels cannot end with a dash");
}
}
/** Domain labels cannot be longer than 63 characters. */
static class DomainLabelTooLongException extends ParameterValuePolicyErrorException {
static class DomainLabelTooLongException extends ParameterValueSyntaxErrorException {
public DomainLabelTooLongException() {
super("Domain labels cannot be longer than 63 characters");
}
}
/** No part of a domain name can be empty. */
static class EmptyDomainNamePartException extends ParameterValuePolicyErrorException {
static class EmptyDomainNamePartException extends ParameterValueSyntaxErrorException {
public EmptyDomainNamePartException() {
super("No part of a domain name can be empty");
}
}
/** Domain name starts with xn-- but is not a valid IDN. */
static class InvalidPunycodeException extends ParameterValuePolicyErrorException {
static class InvalidPunycodeException extends ParameterValueSyntaxErrorException {
public InvalidPunycodeException() {
super("Domain name starts with xn-- but is not a valid IDN");
}
@@ -21,6 +21,8 @@ import static com.google.common.collect.Sets.union;
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
import static google.registry.flows.FlowUtils.persistEntityChanges;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.checkExistingValueNotAdded;
import static google.registry.flows.ResourceFlowUtils.checkNonexistentValueNotRemoved;
import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable;
@@ -248,6 +250,8 @@ public final class DomainUpdateFlow implements MutatingFlow {
checkSameValuesNotAddedAndRemoved(add.getNameservers(), remove.getNameservers());
checkSameValuesNotAddedAndRemoved(add.getContacts(), remove.getContacts());
checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues());
checkExistingValueNotAdded(add.getNameservers(), domain.getNameservers());
checkNonexistentValueNotRemoved(remove.getNameservers(), domain.getNameservers());
Change change = command.getInnerChange();
Optional<SecDnsUpdateExtension> secDnsUpdate =
eppInput.getSingleExtension(SecDnsUpdateExtension.class);
@@ -65,6 +65,7 @@ public final class HostCheckFlow implements TransactionalFlow {
ForeignKeyUtils.loadKeys(Host.class, hostnames, clock.nowUtc()).keySet();
ImmutableList.Builder<HostCheck> checks = new ImmutableList.Builder<>();
for (String hostname : hostnames) {
HostFlowUtils.validateHostName(hostname);
boolean unused = !existingIds.contains(hostname);
checks.add(HostCheck.create(unused, hostname, unused ? null : "In use"));
}
@@ -116,6 +116,7 @@ public final class HostCreateFlow implements MutatingFlow {
? new SubordinateHostMustHaveIpException()
: new UnexpectedExternalHostIpException();
}
HostFlowUtils.validateInetAddresses(command.getInetAddresses());
Host newHost =
new Host.Builder()
.setCreationRegistrarId(registrarId)
@@ -14,12 +14,15 @@
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.PreconditionsUtils.checkArgumentNotNull;
import static java.util.stream.Collectors.joining;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import google.registry.flows.EppException;
import google.registry.flows.EppException.AuthorizationErrorException;
@@ -32,12 +35,17 @@ import google.registry.model.ForeignKeyUtils;
import google.registry.model.domain.Domain;
import google.registry.model.eppcommon.StatusValue;
import google.registry.util.Idn;
import java.net.InetAddress;
import java.util.Optional;
import org.joda.time.DateTime;
/** Static utility functions for host flows. */
public class HostFlowUtils {
/** Validator for ASCII lowercase letters, digits, and "-_", allowing "." as a separator */
private static final CharMatcher HOST_NAME_ALLOWED_CHARS =
CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('0', '9').or(CharMatcher.anyOf("-._")));
/** Checks that a host name is valid. */
public static InternetDomainName validateHostName(String name) throws EppException {
checkArgumentNotNull(name, "Must specify host name to validate");
@@ -53,6 +61,9 @@ public class HostFlowUtils {
if (!name.equals(hostNamePunyCoded)) {
throw new HostNameNotPunyCodedException(hostNamePunyCoded);
}
if (!HOST_NAME_ALLOWED_CHARS.matchesAllOf(name)) {
throw new BadHostNameCharacterException();
}
InternetDomainName hostName = InternetDomainName.from(name);
if (!name.equals(hostName.toString())) {
throw new HostNameNotNormalizedException(hostName.toString());
@@ -71,6 +82,7 @@ public class HostFlowUtils {
if (hostName.parts().size() < effectiveTld.parts().size() + 2) {
throw new HostNameTooShallowException();
}
validateFirstLabel(hostName.parts().getFirst());
return hostName;
} catch (IllegalArgumentException e) {
throw new InvalidHostNameException();
@@ -98,6 +110,24 @@ public class HostFlowUtils {
return superordinateDomain;
}
/** Makes sure that no provided IP addresses are local / loopback addresses. */
public static void validateInetAddresses(ImmutableSet<InetAddress> inetAddresses)
throws EppException {
if (inetAddresses == null) {
return;
}
if (inetAddresses.stream().anyMatch(InetAddress::isLoopbackAddress)) {
throw new LoopbackIpNotValidForHostException();
}
}
/** Loopback IPs are not valid for hosts. */
static class LoopbackIpNotValidForHostException extends ParameterValuePolicyErrorException {
public LoopbackIpNotValidForHostException() {
super("Loopback IPs are not valid for hosts");
}
}
/** Superordinate domain for this hostname does not exist. */
static class SuperordinateDomainDoesNotExistException extends ObjectDoesNotExistException {
public SuperordinateDomainDoesNotExistException(String domainName) {
@@ -180,4 +210,11 @@ public class HostFlowUtils {
String.format("Host names must be in normalized format; expected %s", expectedHostName));
}
}
/** Host names can only contain a-z, 0-9, '.', '_', and '-'. */
static class BadHostNameCharacterException extends ParameterValueSyntaxErrorException {
public BadHostNameCharacterException() {
super("Host names can only contain a-z, 0-9, '.', '_', and '-'");
}
}
}
@@ -20,6 +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.checkExistingValueNotAdded;
import static google.registry.flows.ResourceFlowUtils.checkNonexistentValueNotRemoved;
import static google.registry.flows.ResourceFlowUtils.checkSameValuesNotAddedAndRemoved;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
import static google.registry.flows.ResourceFlowUtils.verifyAllStatusesAreClientSettable;
@@ -161,6 +163,9 @@ public final class HostUpdateFlow implements MutatingFlow {
AddRemove remove = command.getInnerRemove();
checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues());
checkSameValuesNotAddedAndRemoved(add.getInetAddresses(), remove.getInetAddresses());
checkExistingValueNotAdded(add.getInetAddresses(), existingHost.getInetAddresses());
checkNonexistentValueNotRemoved(remove.getInetAddresses(), existingHost.getInetAddresses());
HostFlowUtils.validateInetAddresses(add.getInetAddresses());
VKey<Domain> newSuperordinateDomainKey =
newSuperordinateDomain.map(Domain::createVKey).orElse(null);
// If the superordinateDomain field is changing, set the lastSuperordinateChange to now.
@@ -15,6 +15,8 @@
package google.registry.model.domain.fee;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.flows.EppException;
import google.registry.model.eppinput.EppInput.CommandExtension;
import org.joda.money.CurrencyUnit;
@@ -42,4 +44,11 @@ public interface FeeCheckCommandExtension<
ImmutableList<C> getItems();
R createResponse(ImmutableList<? extends FeeCheckResponseExtensionItem> items);
default R createResponse(
ImmutableList<? extends FeeCheckResponseExtensionItem> items,
ImmutableSet<CurrencyUnit> currenciesSeen)
throws EppException {
return createResponse(items);
}
}
@@ -18,7 +18,6 @@ import com.google.common.base.Ascii;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.FeeCheckCommandExtensionItem;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.Locale;
import java.util.Optional;
@@ -32,12 +31,13 @@ import org.joda.time.DateTime;
* <pre>{@code
* <fee:command name="renew" phase="sunrise" subphase="hello">
* <fee:period unit="y">1</fee:period>
* <fee:class>premium</fee:class>
* <fee:date>2017-05-17T13:22:21.0Z</fee:date>
* </fee:command>
* }</pre>
*
* <p>The `feeClass` and `feeDate` attributes that are present in version 0.12 are removed from this
* version.
*/
@XmlType(propOrder = {"period", "feeClass", "feeDate"})
@XmlType(propOrder = {"period"})
public class FeeCheckCommandExtensionItemStdV1 extends FeeCheckCommandExtensionItem {
/** The default validity period (if not specified) is 1 year for all operations. */
@@ -50,12 +50,6 @@ public class FeeCheckCommandExtensionItemStdV1 extends FeeCheckCommandExtensionI
@XmlAttribute String subphase;
@XmlElement(name = "class")
String feeClass;
@XmlElement(name = "date")
DateTime feeDate;
/** Version 1.0 does not support domain name or currency in fee extension items. */
@Override
public boolean isDomainNameSupported() {
@@ -107,6 +101,6 @@ public class FeeCheckCommandExtensionItemStdV1 extends FeeCheckCommandExtensionI
@Override
public Optional<DateTime> getEffectiveDate() {
return Optional.ofNullable(feeDate);
return Optional.empty();
}
}
@@ -17,6 +17,9 @@ package google.registry.model.domain.feestdv1;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.flows.EppException;
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.fee.FeeCheckCommandExtension;
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
@@ -51,13 +54,33 @@ public class FeeCheckCommandExtensionStdV1 extends ImmutableObject
@Override
public FeeCheckResponseExtensionStdV1 createResponse(
ImmutableList<? extends FeeCheckResponseExtensionItem> items) {
throw new UnsupportedOperationException("FeeCheckCommandExtensionStdV1 requires a currency");
}
@Override
public FeeCheckResponseExtensionStdV1 createResponse(
ImmutableList<? extends FeeCheckResponseExtensionItem> items,
ImmutableSet<CurrencyUnit> currenciesSeen)
throws EppException {
ImmutableList.Builder<FeeCheckResponseExtensionItemStdV1> builder =
new ImmutableList.Builder<>();
for (FeeCheckResponseExtensionItem item : items) {
if (item instanceof FeeCheckResponseExtensionItemStdV1) {
builder.add((FeeCheckResponseExtensionItemStdV1) item);
if (item instanceof FeeCheckResponseExtensionItemStdV1 stdv1Item) {
builder.add(stdv1Item);
}
}
return FeeCheckResponseExtensionStdV1.create(currency, builder.build());
if (currenciesSeen.size() > 1) {
throw new MultipleCurrenciesCannotBeCheckedException();
}
return FeeCheckResponseExtensionStdV1.create(currenciesSeen.iterator().next(), builder.build());
}
/** Domains across multiple currencies cannot be checked simultaneously. */
public static class MultipleCurrenciesCannotBeCheckedException
extends ParameterValuePolicyErrorException {
public MultipleCurrenciesCannotBeCheckedException() {
// The fee extension 1.0 only supports one currency shared across all results
super("Domains across multiple currencies cannot be checked simultaneously");
}
}
}
@@ -24,13 +24,11 @@ import google.registry.model.domain.Period;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.List;
import org.joda.time.DateTime;
/** The version 1.0 response command entity for a domain check on a single resource. */
@XmlType(propOrder = {"period", "fee", "feeClass", "effectiveDate", "notAfterDate"})
@XmlType(propOrder = {"period", "fee"})
public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
/** The command that was checked. */
@@ -53,26 +51,6 @@ public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
*/
List<Fee> fee;
/**
* The type of the fee.
*
* <p>We will use "premium" for fees on premium names, and omit the field otherwise.
*/
@XmlElement(name = "class")
String feeClass;
/** The effective date that the check is to be performed on (if specified in the query). */
@XmlElement(name = "date")
DateTime effectiveDate;
/** The date after which the quoted fee is no longer valid (if applicable). */
@XmlElement(name = "notAfter")
DateTime notAfterDate;
public String getFeeClass() {
return feeClass;
}
/** Builder for {@link FeeCheckResponseExtensionItemCommandStdV1}. */
public static class Builder extends Buildable.Builder<FeeCheckResponseExtensionItemCommandStdV1> {
@@ -96,24 +74,9 @@ public class FeeCheckResponseExtensionItemCommandStdV1 extends ImmutableObject {
return this;
}
public Builder setEffectiveDate(DateTime effectiveDate) {
getInstance().effectiveDate = effectiveDate;
return this;
}
public Builder setNotAfterDate(DateTime notAfterDate) {
getInstance().notAfterDate = notAfterDate;
return this;
}
public Builder setFee(List<Fee> fees) {
getInstance().fee = forceEmptyToNull(ImmutableList.copyOf(fees));
return this;
}
public Builder setClass(String feeClass) {
getInstance().feeClass = feeClass;
return this;
}
}
}
@@ -17,20 +17,18 @@ package google.registry.model.domain.feestdv1;
import static google.registry.util.CollectionUtils.forceEmptyToNull;
import com.google.common.collect.ImmutableList;
import google.registry.model.domain.DomainObjectSpec;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import jakarta.xml.bind.annotation.XmlType;
import org.joda.time.DateTime;
/** The version 1.0 response for a domain check on a single resource. */
@XmlType(propOrder = {"object", "command"})
@XmlType(propOrder = {"objID", "feeClass", "command"})
public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensionItem {
/** The domain that was checked. */
DomainObjectSpec object;
String objID;
/** The command that was checked. */
FeeCheckResponseExtensionItemCommandStdV1 command;
@@ -53,15 +51,6 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
return super.getFees();
}
/**
* This method is not annotated for JAXB because this version of the extension doesn't support
* "feeClass" and because the data comes off of the command object rather than a field.
*/
@Override
public String getFeeClass() {
return command.getFeeClass();
}
/** Builder for {@link FeeCheckResponseExtensionItemStdV1}. */
public static class Builder
extends FeeCheckResponseExtensionItem.Builder<FeeCheckResponseExtensionItemStdV1> {
@@ -91,13 +80,13 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
@Override
public Builder setClass(String feeClass) {
commandBuilder.setClass(feeClass);
super.setClass(feeClass);
return this;
}
@Override
public Builder setDomainNameIfSupported(String name) {
getInstance().object = new DomainObjectSpec(name);
getInstance().objID = name;
return this;
}
@@ -106,17 +95,5 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
getInstance().command = commandBuilder.build();
return super.build();
}
@Override
public Builder setEffectiveDateIfSupported(DateTime effectiveDate) {
commandBuilder.setEffectiveDate(effectiveDate);
return this;
}
@Override
public Builder setNotAfterDateIfSupported(DateTime notAfterDate) {
commandBuilder.setNotAfterDate(notAfterDate);
return this;
}
}
}
@@ -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);
@@ -33,6 +33,7 @@ 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.RegistryEnvironment;
import jakarta.xml.bind.annotation.XmlSchema;
import java.util.EnumSet;
@@ -48,30 +49,50 @@ 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_PRODUCTION,
ONLY_IN_NON_PRODUCTION,
NONE
}
/** Enum representing valid service extensions that are recognized by the server. */
public enum ServiceExtension {
LAUNCH_EXTENSION_1_0(LaunchCreateExtension.class, null, true),
REDEMPTION_GRACE_PERIOD_1_0(RgpUpdateExtension.class, null, true),
SECURE_DNS_1_1(SecDnsCreateExtension.class, null, true),
FEE_0_6(FeeCheckCommandExtensionV06.class, FeeCheckResponseExtensionV06.class, true),
FEE_0_11(FeeCheckCommandExtensionV11.class, FeeCheckResponseExtensionV11.class, true),
FEE_0_12(FeeCheckCommandExtensionV12.class, FeeCheckResponseExtensionV12.class, true),
FEE_1_00(FeeCheckCommandExtensionStdV1.class, FeeCheckResponseExtensionStdV1.class, false),
METADATA_1_0(MetadataExtension.class, null, false);
LAUNCH_EXTENSION_1_0(LaunchCreateExtension.class, null, ServiceExtensionVisibility.ALL),
REDEMPTION_GRACE_PERIOD_1_0(RgpUpdateExtension.class, null, ServiceExtensionVisibility.ALL),
SECURE_DNS_1_1(SecDnsCreateExtension.class, null, ServiceExtensionVisibility.ALL),
FEE_0_6(
FeeCheckCommandExtensionV06.class,
FeeCheckResponseExtensionV06.class,
ServiceExtensionVisibility.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 +107,20 @@ public class ProtocolDefinition {
return uri;
}
public boolean getVisible() {
return visible;
}
/** Returns the namespace URI of the command extension class. */
public static String getCommandExtensionUri(Class<? extends CommandExtension> clazz) {
return clazz.getPackage().getAnnotation(XmlSchema.class).namespace();
}
private boolean isVisible() {
return switch (visibility) {
case ALL -> true;
case ONLY_IN_PRODUCTION -> RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION);
case ONLY_IN_NON_PRODUCTION ->
!RegistryEnvironment.get().equals(RegistryEnvironment.PRODUCTION);
case NONE -> false;
};
}
}
/**
@@ -111,9 +138,8 @@ 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)
EnumSet.allOf(ServiceExtension.class).stream()
.filter(ServiceExtension::isVisible)
.map(ServiceExtension::getUri)
.collect(toImmutableSet());
@@ -21,6 +21,7 @@ import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import google.registry.model.ImmutableObject;
import google.registry.tmch.RstTmchUtils;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
@@ -71,6 +72,11 @@ public class SignedMarkRevocationList extends ImmutableObject {
return CACHE.get();
}
// TODO(b/412715713): remove the tld parameter when RST completes.
public static SignedMarkRevocationList get(String tld) {
return RstTmchUtils.getSmdrList(tld).orElseGet(SignedMarkRevocationList::get);
}
/** Create a new {@link SignedMarkRevocationList} without saving it. */
public static SignedMarkRevocationList create(
DateTime creationTime, ImmutableMap<String, DateTime> revokes) {
@@ -1034,12 +1034,13 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
return this;
}
public static final Pattern ROID_SUFFIX_PATTERN = Pattern.compile("^[A-Z\\d_]{1,8}$");
public static final Pattern ROID_SUFFIX_PATTERN = Pattern.compile("^[A-Z\\d]{1,8}$");
public Builder setRoidSuffix(String roidSuffix) {
checkArgument(
ROID_SUFFIX_PATTERN.matcher(roidSuffix).matches(),
"ROID suffix must be in format %s",
"ROID suffix %s must be in format %s",
roidSuffix,
ROID_SUFFIX_PATTERN.pattern());
getInstance().roidSuffix = roidSuffix;
return this;
@@ -22,6 +22,7 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import google.registry.model.CacheUtils;
import google.registry.tmch.RstTmchUtils;
import java.time.Duration;
import java.util.Optional;
@@ -72,6 +73,11 @@ public class ClaimsListDao {
return CACHE.get(ClaimsListDao.class);
}
// TODO(b/412715713): remove the tld parameter when RST completes.
public static ClaimsList get(String tld) {
return RstTmchUtils.getClaimsList(tld).orElseGet(ClaimsListDao::get);
}
/**
* Returns the most recent revision of the {@link ClaimsList} in SQL or an empty list if it
* doesn't exist.
@@ -62,6 +62,9 @@ import google.registry.module.ReadinessProbeAction.ReadinessProbeActionFrontend;
import google.registry.module.ReadinessProbeAction.ReadinessProbeActionPubApi;
import google.registry.module.ReadinessProbeAction.ReadinessProbeConsoleAction;
import google.registry.monitoring.whitebox.WhiteboxModule;
import google.registry.mosapi.GetServiceStateAction;
import google.registry.mosapi.TriggerServiceStateAction;
import google.registry.mosapi.module.MosApiRequestModule;
import google.registry.rdap.RdapAutnumAction;
import google.registry.rdap.RdapDomainAction;
import google.registry.rdap.RdapDomainSearchAction;
@@ -151,6 +154,7 @@ import google.registry.ui.server.console.settings.SecurityAction;
EppToolModule.class,
IcannReportingModule.class,
LoadTestModule.class,
MosApiRequestModule.class,
RdapModule.class,
RdeModule.class,
ReportingModule.class,
@@ -232,6 +236,8 @@ interface RequestComponent {
GenerateZoneFilesAction generateZoneFilesAction();
GetServiceStateAction getServiceStateAction();
IcannReportingStagingAction icannReportingStagingAction();
IcannReportingUploadAction icannReportingUploadAction();
@@ -334,6 +340,8 @@ interface RequestComponent {
TmchSmdrlAction tmchSmdrlAction();
TriggerServiceStateAction triggerServiceStateAction();
UpdateRegistrarRdapBaseUrlsAction updateRegistrarRdapBaseUrlsAction();
UpdateUserGroupAction updateUserGroupAction();
@@ -0,0 +1,68 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi;
import com.google.common.net.MediaType;
import com.google.gson.Gson;
import google.registry.request.Action;
import google.registry.request.HttpException.ServiceUnavailableException;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
import java.util.Optional;
/** An action that returns the current MoSAPI service state for a given TLD or all TLDs. */
@Action(
service = Action.Service.BACKEND,
path = GetServiceStateAction.PATH,
method = Action.Method.GET,
auth = Auth.AUTH_ADMIN)
public class GetServiceStateAction implements Runnable {
public static final String PATH = "/_dr/mosapi/getServiceState";
public static final String TLD_PARAM = "tld";
private final MosApiStateService stateService;
private final Response response;
private final Gson gson;
private final Optional<String> tld;
@Inject
public GetServiceStateAction(
MosApiStateService stateService,
Response response,
Gson gson,
@Parameter(TLD_PARAM) Optional<String> tld) {
this.stateService = stateService;
this.response = response;
this.gson = gson;
this.tld = tld;
}
@Override
public void run() {
response.setContentType(MediaType.JSON_UTF_8);
try {
if (tld.isPresent()) {
response.setPayload(gson.toJson(stateService.getServiceStateSummary(tld.get())));
} else {
response.setPayload(gson.toJson(stateService.getAllServiceStateSummaries()));
}
} catch (MosApiException e) {
throw new ServiceUnavailableException("Error fetching MoSAPI service state.");
}
}
}
@@ -12,7 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi.model;
package google.registry.mosapi;
import com.google.gson.annotations.Expose;
/**
* Represents the generic JSON error response from the MoSAPI service for a 400 Bad Request.
@@ -20,4 +22,5 @@ package google.registry.mosapi.model;
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification, Section
* 8</a>
*/
public record MosApiErrorResponse(String resultCode, String message, String description) {}
public record MosApiErrorResponse(
@Expose String resultCode, @Expose String message, @Expose String description) {}
@@ -17,7 +17,6 @@ package google.registry.mosapi;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import google.registry.mosapi.model.MosApiErrorResponse;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -42,6 +41,11 @@ public class MosApiException extends IOException {
this.errorResponse = null;
}
public MosApiException(String message) {
super(message);
this.errorResponse = null;
}
public Optional<MosApiErrorResponse> getErrorResponse() {
return Optional.ofNullable(errorResponse);
}
@@ -0,0 +1,34 @@
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi;
import com.google.common.flogger.FluentLogger;
import google.registry.mosapi.MosApiModels.TldServiceState;
import jakarta.inject.Inject;
import java.util.List;
/** Metrics Exporter for MoSAPI. */
public class MosApiMetrics {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject
public MosApiMetrics() {}
public void recordStates(List<TldServiceState> states) {
// b/467541269: Logic to push status to Cloud Monitoring goes here
logger.atInfo().log("MoSAPI record metrics logic will be implemented from here");
}
}
@@ -0,0 +1,122 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/** Data models for ICANN MoSAPI. */
public final class MosApiModels {
private MosApiModels() {}
/**
* A wrapper response containing the state summaries of all monitored services.
*
* <p>This corresponds to the collection of service statuses returned when monitoring the state of
* a TLD
*
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
* Section 5.1</a>
*/
public record AllServicesStateResponse(
// A list of state summaries for each monitored service (e.g. DNS, RDDS, etc.)
@Expose List<ServiceStateSummary> serviceStates) {
public AllServicesStateResponse {
serviceStates = nullToEmptyImmutableCopy(serviceStates);
}
}
/**
* A summary of a service incident.
*
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
* Section 5.1</a>
*/
public record IncidentSummary(
@Expose String incidentID,
@Expose long startTime,
@Expose boolean falsePositive,
@Expose String state,
@Expose @Nullable Long endTime) {}
/**
* A curated summary of the service state for a TLD.
*
* <p>This class aggregates the high-level status of a TLD and details of any active incidents
* affecting specific services (like DNS or RDDS), based on the data structures defined in the
* MoSAPI specification.
*
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
* Section 5.1</a>
*/
public record ServiceStateSummary(
@Expose String tld,
@Expose String overallStatus,
@Expose List<ServiceStatus> activeIncidents) {
public ServiceStateSummary {
activeIncidents = nullToEmptyImmutableCopy(activeIncidents);
}
}
/** Represents the status of a single monitored service. */
public record ServiceStatus(
/**
* A JSON string that contains the status of the Service as seen from the monitoring system.
* Possible values include "Up", "Down", "Disabled", "UP-inconclusive-no-data", etc.
*/
@Expose String status,
// A JSON number that contains the current percentage of the Emergency Threshold
// of the Service. A value of "0" specifies that there are no Incidents
// affecting the threshold.
@Expose double emergencyThreshold,
@Expose List<IncidentSummary> incidents) {
public ServiceStatus {
incidents = nullToEmptyImmutableCopy(incidents);
}
}
/**
* Represents the overall health of all monitored services for a TLD.
*
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
* Section 5.1</a>
*/
public record TldServiceState(
@Expose String tld,
long lastUpdateApiDatabase,
// A JSON string that contains the status of the TLD as seen from the monitoring system
@Expose String status,
// A JSON object containing detailed information for each potential monitored service (i.e.,
// DNS,
// RDDS, EPP, DNSSEC, RDAP).
@Expose @SerializedName("testedServices") Map<String, ServiceStatus> serviceStatuses) {
public TldServiceState {
serviceStatuses = nullToEmptyImmutableCopy(serviceStatuses);
}
}
}
@@ -0,0 +1,154 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.mosapi.MosApiModels.AllServicesStateResponse;
import google.registry.mosapi.MosApiModels.ServiceStateSummary;
import google.registry.mosapi.MosApiModels.ServiceStatus;
import google.registry.mosapi.MosApiModels.TldServiceState;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
/** A service that provides business logic for interacting with MoSAPI Service State. */
public class MosApiStateService {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final ServiceMonitoringClient serviceMonitoringClient;
private final ExecutorService tldExecutor;
private final ImmutableSet<String> tlds;
private final MosApiMetrics mosApiMetrics;
private static final String DOWN_STATUS = "Down";
private static final String FETCH_ERROR_STATUS = "ERROR";
@Inject
public MosApiStateService(
ServiceMonitoringClient serviceMonitoringClient,
MosApiMetrics mosApiMetrics,
@Config("mosapiTlds") ImmutableSet<String> tlds,
@Named("mosapiTldExecutor") ExecutorService tldExecutor) {
this.serviceMonitoringClient = serviceMonitoringClient;
this.mosApiMetrics = mosApiMetrics;
this.tlds = tlds;
this.tldExecutor = tldExecutor;
}
/** Fetches and transforms the service state for a given TLD into a summary. */
public ServiceStateSummary getServiceStateSummary(String tld) throws MosApiException {
TldServiceState rawState = serviceMonitoringClient.getTldServiceState(tld);
return transformToSummary(rawState);
}
/** Fetches and transforms the service state for all configured TLDs. */
public AllServicesStateResponse getAllServiceStateSummaries() {
ImmutableList<CompletableFuture<ServiceStateSummary>> futures =
tlds.stream()
.map(
tld ->
CompletableFuture.supplyAsync(
() -> {
try {
return getServiceStateSummary(tld);
} catch (MosApiException e) {
logger.atWarning().withCause(e).log(
"Failed to get service state for TLD %s.", tld);
// we don't want to throw exception if fetch failed
return new ServiceStateSummary(tld, FETCH_ERROR_STATUS, null);
}
},
tldExecutor))
.collect(ImmutableList.toImmutableList());
ImmutableList<ServiceStateSummary> summaries =
futures.stream()
.map(CompletableFuture::join) // Waits for all tasks to complete
.collect(toImmutableList());
return new AllServicesStateResponse(summaries);
}
private ServiceStateSummary transformToSummary(TldServiceState rawState) {
ImmutableList<ServiceStatus> activeIncidents = ImmutableList.of();
if (DOWN_STATUS.equalsIgnoreCase(rawState.status())) {
activeIncidents =
rawState.serviceStatuses().entrySet().stream()
.filter(
entry -> {
ServiceStatus serviceStatus = entry.getValue();
return serviceStatus.incidents() != null
&& !serviceStatus.incidents().isEmpty();
})
.map(
entry ->
new ServiceStatus(
// key is the service name
entry.getKey(),
entry.getValue().emergencyThreshold(),
entry.getValue().incidents()))
.collect(toImmutableList());
}
return new ServiceStateSummary(rawState.tld(), rawState.status(), activeIncidents);
}
/** Triggers monitoring exposure for all configured TLDs. */
public void triggerMetricsForAllServiceStateSummaries() {
ImmutableList<CompletableFuture<TldServiceState>> futures =
tlds.stream()
.map(
tld ->
CompletableFuture.supplyAsync(
() -> {
try {
return serviceMonitoringClient.getTldServiceState(tld);
} catch (MosApiException e) {
// Log the error but don't rethrow as RuntimeException
logger.atWarning().withCause(e).log(
"Failed to fetch state for TLD: %s", tld);
return null; // Return null so the stream keeps moving
}
},
tldExecutor))
.collect(toImmutableList());
List<TldServiceState> allStates =
futures.stream()
.map(CompletableFuture::join)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (!allStates.isEmpty()) {
try {
mosApiMetrics.recordStates(allStates);
} catch (Exception e) {
logger.atSevere().withCause(e).log("Failed to submit MoSAPI metrics batch.");
}
} else {
logger.atWarning().log("No successful TLD states fetched; skipping metrics push.");
}
}
}
@@ -0,0 +1,80 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi;
import com.google.common.base.Throwables;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import google.registry.mosapi.MosApiModels.TldServiceState;
import jakarta.inject.Inject;
import java.io.IOException;
import java.util.Collections;
import okhttp3.Response;
import okhttp3.ResponseBody;
/** Facade for MoSAPI's service monitoring endpoints. */
public class ServiceMonitoringClient {
private static final String MONITORING_STATE_ENDPOINT = "v2/monitoring/state";
private final MosApiClient mosApiClient;
private final Gson gson;
@Inject
public ServiceMonitoringClient(MosApiClient mosApiClient, Gson gson) {
this.mosApiClient = mosApiClient;
this.gson = gson;
}
/**
* Fetches the current state of all monitored services for a given TLD.
*
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
* Section 5.1</a>
*/
public TldServiceState getTldServiceState(String tld) throws MosApiException {
try (Response response =
mosApiClient.sendGetRequest(
tld, MONITORING_STATE_ENDPOINT, Collections.emptyMap(), Collections.emptyMap())) {
ResponseBody responseBody = response.body();
if (responseBody == null) {
throw new MosApiException(
String.format(
"MoSAPI Service Monitoring API " + "returned an empty body with status: %d",
response.code()));
}
String bodyString = responseBody.string();
if (!response.isSuccessful()) {
throw parseErrorResponse(response.code(), bodyString);
}
return gson.fromJson(bodyString, TldServiceState.class);
} catch (IOException | JsonParseException e) {
Throwables.throwIfInstanceOf(e, MosApiException.class);
// Catch Gson's runtime exceptions (parsing errors) and wrap them
throw new MosApiException("Failed to parse TLD service state response", e);
}
}
/** Parses an unsuccessful MoSAPI response into a domain-specific {@link MosApiException}. */
private MosApiException parseErrorResponse(int statusCode, String bodyString) {
try {
MosApiErrorResponse error = gson.fromJson(bodyString, MosApiErrorResponse.class);
return MosApiException.create(error);
} catch (JsonParseException e) {
return new MosApiException(
String.format("MoSAPI json parsing error (%d): %s", statusCode, bodyString), e);
}
}
}
@@ -0,0 +1,59 @@
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.request.Action;
import google.registry.request.HttpException.InternalServerErrorException;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import jakarta.inject.Inject;
/**
* An action that triggers Metrics action for the current MoSAPI service state result for all TLDs.
*/
@Action(
service = Action.Service.BACKEND,
path = TriggerServiceStateAction.PATH,
method = Action.Method.GET,
auth = Auth.AUTH_ADMIN)
public class TriggerServiceStateAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public static final String PATH = "/_dr/task/triggerMosApiServiceState";
private final MosApiStateService stateService;
private final Response response;
@Inject
public TriggerServiceStateAction(MosApiStateService stateService, Response response) {
this.stateService = stateService;
this.response = response;
}
@Override
public void run() {
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
try {
stateService.triggerMetricsForAllServiceStateSummaries();
response.setStatus(200);
response.setPayload("MoSAPI metrics triggered successfully for all TLDs.");
} catch (Exception e) {
logger.atSevere().withCause(e).log("Error triggering MoSAPI metrics.");
throw new InternalServerErrorException("Failed to process MoSAPI metrics.");
}
}
}
@@ -32,6 +32,8 @@ import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
@@ -184,4 +186,21 @@ public final class MosApiModule {
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
.build();
}
/**
* Provides a fixed thread pool for parallel TLD processing.
*
* <p>Strictly bound to 4 threads to comply with MoSAPI session limits (4 concurrent sessions per
* certificate). This is used by MosApiStateService to fetch data in parallel.
*
* @see <a href="https://www.icann.org/mosapi-specification.pdf">ICANN MoSAPI Specification,
* Section 12.3</a>
*/
@Provides
@Singleton
@Named("mosapiTldExecutor")
static ExecutorService provideMosapiTldExecutor(
@Config("mosapiTldThreadCnt") int threadPoolSize) {
return Executors.newFixedThreadPool(threadPoolSize);
}
}
@@ -0,0 +1,33 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi.module;
import static google.registry.request.RequestParameters.extractOptionalParameter;
import dagger.Module;
import dagger.Provides;
import google.registry.request.Parameter;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
/** Dagger module for MoSAPI requests. */
@Module
public final class MosApiRequestModule {
@Provides
@Parameter("tld")
static Optional<String> provideTld(HttpServletRequest req) {
return extractOptionalParameter(req, "tld");
}
}
@@ -43,6 +43,15 @@ public enum IdnTableEnum {
*/
UNCONFUSABLE_LATIN("unconfusable_latin.txt"),
/**
* ICANN LGR 2025 Latin, but with confusable characters removed.
*
* <p>This is based on <a
* href="https://www.icann.org/sites/default/files/packages/lgr/lgr-second-level-latin-full-variant-script-24jan24-en.html">ICANN's
* LGR table</a>, but is simpler.
*/
AUGMENTED_LATIN("augmented_latin.txt"),
/**
* Japanese, as used on our existing TLD launches prior to 2023.
*
@@ -1,4 +1,4 @@
# URL: https://www.iana.org/domains/idn-tables/tables/google_latn_1.0.txt
# URL: https://www.iana.org/domains/idn-tables/tables/google_latn_3.0.txt
# Policy: https://www.registry.google/about/policies/domainabuse/
U+002D # HYPHEN-MINUS
U+0030 # DIGIT ZERO
@@ -0,0 +1,120 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tmch;
import static com.google.common.base.Suppliers.memoize;
import static com.google.common.io.Resources.getResource;
import static com.google.common.io.Resources.readLines;
import static google.registry.tmch.RstTmchUtils.RstEnvironment.OTE;
import static google.registry.tmch.RstTmchUtils.RstEnvironment.PROD;
import static google.registry.util.RegistryEnvironment.SANDBOX;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import google.registry.model.smd.SignedMarkRevocationList;
import google.registry.model.tmch.ClaimsList;
import google.registry.util.RegistryEnvironment;
import java.io.IOException;
import java.net.URL;
import java.util.Locale;
import java.util.Optional;
/**
* Utilities supporting TMCH-related RST testing in the Sandbox environment.
*
* <p>For logistic reasons we must conduct RST testing in the Sandbox environments. RST tests
* require the use of special labels hosted on their website. To isolate these labels from regular
* customers conducting onboarding tests, we manually download the test files as resources, and
* serve them up only to RST TLDs.
*/
public class RstTmchUtils {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/**
* The RST environments.
*
* <p>We conduct both OTE and PROD RST tests in Sandbox.
*/
enum RstEnvironment {
OTE,
PROD
}
private static final ImmutableMap<RstEnvironment, Supplier<Optional<ClaimsList>>> CLAIMS_CACHE =
ImmutableMap.of(
OTE, memoize(() -> getClaimsList(OTE)), PROD, memoize(() -> getClaimsList(PROD)));
private static final ImmutableMap<RstEnvironment, Supplier<Optional<SignedMarkRevocationList>>>
SMDRL_CACHE =
ImmutableMap.of(
OTE, memoize(() -> getSmdrList(OTE)), PROD, memoize(() -> getSmdrList(PROD)));
/** Returns appropriate test labels if {@code tld} is for RST testing; otherwise returns empty. */
public static Optional<ClaimsList> getClaimsList(String tld) {
return getRstEnvironment(tld).map(CLAIMS_CACHE::get).flatMap(Supplier::get);
}
/** Returns appropriate test labels if {@code tld} is for RST testing; otherwise returns empty. */
public static Optional<SignedMarkRevocationList> getSmdrList(String tld) {
return getRstEnvironment(tld).map(SMDRL_CACHE::get).flatMap(Supplier::get);
}
static Optional<RstEnvironment> getRstEnvironment(String tld) {
if (!RegistryEnvironment.get().equals(SANDBOX)) {
return Optional.empty();
}
if (tld.startsWith("cc-rst-test-")) {
return Optional.of(OTE);
}
if (tld.startsWith("zz--")) {
return Optional.of(PROD);
}
return Optional.empty();
}
private static Optional<ClaimsList> getClaimsList(RstEnvironment rstEnvironment) {
if (!RegistryEnvironment.get().equals(SANDBOX)) {
return Optional.empty();
}
String resourceName = rstEnvironment.name().toLowerCase(Locale.ROOT) + ".rst.dnl.csv";
URL resource = getResource(RstTmchUtils.class, resourceName);
try {
return Optional.of(ClaimsListParser.parse(readLines(resource, UTF_8)));
} catch (IOException e) {
// Do not throw.
logger.atSevere().withCause(e).log(
"Could not load Claims list %s for %s in Sandbox.", resourceName, rstEnvironment);
return Optional.empty();
}
}
private static Optional<SignedMarkRevocationList> getSmdrList(RstEnvironment rstEnvironment) {
if (!RegistryEnvironment.get().equals(SANDBOX)) {
return Optional.empty();
}
String resourceName = rstEnvironment.name().toLowerCase(Locale.ROOT) + ".rst.smdrl.csv";
URL resource = getResource(RstTmchUtils.class, resourceName);
try {
return Optional.of(SmdrlCsvParser.parse(readLines(resource, UTF_8)));
} catch (IOException e) {
// Do not throw.
logger.atSevere().withCause(e).log(
"Could not load SMDR list %s for %s in Sandbox.", resourceName, rstEnvironment);
return Optional.empty();
}
}
}
@@ -38,7 +38,6 @@ import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.xml.XMLConstants;
@@ -82,7 +81,7 @@ public class XmlTransformer {
* @param schemaFilenames schema files, used only for validating, and relative to this package.
* @param recognizedClasses the classes that can be used to marshal to and from
*/
public XmlTransformer(List<String> schemaFilenames, Class<?>... recognizedClasses) {
public XmlTransformer(ImmutableList<String> schemaFilenames, Class<?>... recognizedClasses) {
try {
this.jaxbContext = JAXBContext.newInstance(recognizedClasses);
this.schema = loadXmlSchemas(schemaFilenames);
@@ -251,7 +250,7 @@ public class XmlTransformer {
}
/** Creates a single {@link Schema} from multiple {@code .xsd} files. */
public static Schema loadXmlSchemas(List<String> schemaFilenames) {
public static Schema loadXmlSchemas(ImmutableList<String> schemaFilenames) {
try (Closer closer = Closer.create()) {
StreamSource[] sources = new StreamSource[schemaFilenames.size()];
for (int i = 0; i < schemaFilenames.size(); ++i) {
@@ -23,7 +23,7 @@
<element name="renew" type="fee:transformCommandType" />
<element name="renData" type="fee:transformResultType" />
<element name="transfer" type="fee:transformCommandType" />
<element name="trnData" type="fee:transferResultType" />
<element name="trnData" type="fee:transformResultType" />
<element name="update" type="fee:transformCommandType" />
<element name="updData" type="fee:transformResultType" />
<element name="delData" type="fee:transformResultType" />
@@ -33,32 +33,24 @@
<sequence>
<element name="currency" type="fee:currencyType"
minOccurs="0" />
<element name="command" type="fee:commandCheckType"
maxOccurs="unbounded" />
<element name="command" type="fee:commandType"
minOccurs="1" maxOccurs="unbounded" />
</sequence>
</complexType>
<complexType name="commandCheckType">
<sequence>
<element name="period"
type="domain:periodType"
minOccurs="0" />
<element name="class"
type="token"
minOccurs="0" />
<element name="date"
type="dateTime"
minOccurs="0" />
</sequence>
<attribute name="name" type="fee:commandTypeValue" />
<attribute name="phase" type="token" />
<attribute name="subphase" type="token" />
<complexType name="objectIdentifierType">
<simpleContent>
<extension base="eppcom:labelType">
<attribute name="element"
type="NMTOKEN" default="name" />
</extension>
</simpleContent>
</complexType>
<!-- server <check> result -->
<complexType name="chkDataType">
<sequence>
<element name="currency" type="fee:currencyType" minOccurs="0"/>
<element name="currency" type="fee:currencyType" />
<element name="cd" type="fee:objectCDType"
maxOccurs="unbounded" />
</sequence>
@@ -66,47 +58,13 @@
<complexType name="objectCDType">
<sequence>
<element name="object">
<complexType>
<sequence>
<any namespace="##other" processContents="lax"/>
</sequence>
</complexType>
</element>
<element name="command"
type="fee:commandCDType"
maxOccurs="unbounded" />
</sequence>
</complexType>
<complexType name="commandCDType">
<sequence>
<element name="period"
type="domain:periodType"
minOccurs="0" maxOccurs="1" />
<element name="fee"
type="fee:feeType"
<element name="objID" type="fee:objectIdentifierType" />
<element name="class" type="token" minOccurs="0" />
<element name="command" type="fee:commandDataType"
minOccurs="0" maxOccurs="unbounded" />
<element name="credit"
type="fee:creditType"
minOccurs="0" maxOccurs="unbounded" />
<element name="class"
type="token"
minOccurs="0" />
<element name="reason"
type="token"
minOccurs="0" />
<element name="date"
type="dateTime"
minOccurs="0" />
<element name="notAfter"
type="dateTime"
minOccurs="0" />
<element name="reason" type="fee:reasonType" minOccurs="0" />
</sequence>
<attribute name="avail" type="boolean" default="1" />
<attribute name="name" type="fee:commandTypeValue" />
<attribute name="phase" type="token" />
<attribute name="subphase" type="token" />
</complexType>
<!-- general transform (create, renew, update, transfer) command-->
@@ -121,32 +79,15 @@
</sequence>
</complexType>
<!-- general transform (create, renew, update, delete) result -->
<!-- general transform (create, renew, update) result -->
<complexType name="transformResultType">
<sequence>
<element name="currency" type="fee:currencyType" />
<element name="fee" type="fee:feeType"
minOccurs="0" maxOccurs="unbounded" />
<element name="credit" type="fee:creditType"
minOccurs="0" maxOccurs="unbounded" />
<element name="balance" type="fee:balanceType"
<element name="currency" type="fee:currencyType"
minOccurs="0" />
<element name="creditLimit" type="fee:creditLimitType"
minOccurs="0" />
</sequence>
</complexType>
<!-- transfer result -->
<complexType name="transferResultType">
<sequence>
<element name="currency" type="fee:currencyType" />
<!-- only used op="query" responses -->
<element name="period" type="domain:periodType"
minOccurs="0" />
<element name="fee" type="fee:feeType"
maxOccurs="unbounded" />
minOccurs="0" maxOccurs="unbounded" />
<element name="credit" type="fee:creditType"
minOccurs="0" maxOccurs="unbounded" />
<element name="balance" type="fee:balanceType"
@@ -163,10 +104,50 @@
</restriction>
</simpleType>
<simpleType name="commandTypeValue">
<complexType name="commandType">
<sequence>
<element name="period" type="domain:periodType"
minOccurs="0" maxOccurs="1" />
</sequence>
<attribute name="name" type="fee:commandEnum" use="required"/>
<attribute name="customName" type="token"/>
<attribute name="phase" type="token" />
<attribute name="subphase" type="token" />
</complexType>
<complexType name="commandDataType">
<complexContent>
<extension base="fee:commandType">
<sequence>
<element name="fee" type="fee:feeType"
minOccurs="0" maxOccurs="unbounded" />
<element name="credit" type="fee:creditType"
minOccurs="0" maxOccurs="unbounded" />
<element name="reason" type="fee:reasonType"
minOccurs="0" />
</sequence>
<attribute name="standard" type="boolean" default="0" />
</extension>
</complexContent>
</complexType>
<complexType name="reasonType">
<simpleContent>
<extension base="token">
<attribute name="lang" type="language" default="en"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="commandEnum">
<restriction base="token">
<minLength value="3"/>
<maxLength value="16"/>
<enumeration value="create"/>
<enumeration value="delete"/>
<enumeration value="renew"/>
<enumeration value="update"/>
<enumeration value="transfer"/>
<enumeration value="restore"/>
<enumeration value="custom"/>
</restriction>
</simpleType>
@@ -186,9 +167,10 @@
<simpleContent>
<extension base="fee:nonNegativeDecimal">
<attribute name="description"/>
<attribute name="lang" type="language" default="en"/>
<attribute name="refundable" type="boolean" />
<attribute name="grace-period" type="duration" />
<attribute name="applied" default="immediate">
<attribute name="applied">
<simpleType>
<restriction base="token">
<enumeration value="immediate" />
@@ -204,6 +186,7 @@
<simpleContent>
<extension base="fee:negativeDecimal">
<attribute name="description"/>
<attribute name="lang" type="language" default="en"/>
</extension>
</simpleContent>
</complexType>
@@ -0,0 +1,10 @@
1,2024-09-13T02:21:12.0Z
DNL,lookup-key,insertion-datetime
test---validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
test--validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
test-and-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
test-andvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
test-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
testand-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
testandvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
testvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
1 1,2024-09-13T02:21:12.0Z
2 DNL,lookup-key,insertion-datetime
3 test---validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
4 test--validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
5 test-and-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
6 test-andvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
7 test-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
8 testand-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
9 testandvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
10 testvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
@@ -0,0 +1,7 @@
1,2022-11-22T01:49:36.9Z
smd-id,insertion-datetime
0000001761385117375880-65535,2013-07-15T00:00:00.0Z
0000001751501056761969-65535,2017-07-26T10:12:41.9Z
000000541526299609231-65535,2018-05-14T17:52:23.7Z
000000541602140609520-65535,2020-10-08T07:07:25.0Z
000000541669081776937-65535,2022-11-22T01:49:36.9Z
1 1 2022-11-22T01:49:36.9Z
2 smd-id insertion-datetime
3 0000001761385117375880-65535 2013-07-15T00:00:00.0Z
4 0000001751501056761969-65535 2017-07-26T10:12:41.9Z
5 000000541526299609231-65535 2018-05-14T17:52:23.7Z
6 000000541602140609520-65535 2020-10-08T07:07:25.0Z
7 000000541669081776937-65535 2022-11-22T01:49:36.9Z
@@ -0,0 +1,10 @@
1,2024-09-13T02:21:12.0Z
DNL,lookup-key,insertion-datetime
test---validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
test--validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
test-and-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
test-andvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
test-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
testand-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
testandvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
testvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
1 1,2024-09-13T02:21:12.0Z
2 DNL,lookup-key,insertion-datetime
3 test---validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
4 test--validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
5 test-and-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
6 test-andvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
7 test-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
8 testand-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
9 testandvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
10 testvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
@@ -0,0 +1,7 @@
1,2022-11-22T01:49:36.9Z
smd-id,insertion-datetime
0000001761385117375880-65535,2013-07-15T00:00:00.0Z
0000001751501056761969-65535,2017-07-26T10:12:41.9Z
000000541526299609231-65535,2018-05-14T17:52:23.7Z
000000541602140609520-65535,2020-10-08T07:07:25.0Z
000000541669081776937-65535,2022-11-22T01:49:36.9Z
1 1 2022-11-22T01:49:36.9Z
2 smd-id insertion-datetime
3 0000001761385117375880-65535 2013-07-15T00:00:00.0Z
4 0000001751501056761969-65535 2017-07-26T10:12:41.9Z
5 000000541526299609231-65535 2018-05-14T17:52:23.7Z
6 000000541602140609520-65535 2020-10-08T07:07:25.0Z
7 000000541669081776937-65535 2022-11-22T01:49:36.9Z
@@ -335,7 +335,7 @@ class InvoicingPipelineTest {
.build();
persistResource(registrar);
Tld test =
newTld("test", "_TEST", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
newTld("test", "TEST", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
.asBuilder()
.setInvoicingEnabled(true)
.build();
@@ -391,7 +391,7 @@ class InvoicingPipelineTest {
// Test that comments are removed from the .sql file correctly
assertThat(InvoicingPipeline.makeCloudSqlQuery("2017-10"))
.isEqualTo(
"""
"""
SELECT b, r FROM BillingEvent b
JOIN Registrar r ON b.clientId = r.registrarId
@@ -449,13 +449,13 @@ AND cr.id IS NULL
persistResource(registrar3);
Tld test =
newTld("test", "_TEST", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
newTld("test", "TEST", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
.asBuilder()
.setInvoicingEnabled(true)
.build();
persistResource(test);
Tld hello =
newTld("hello", "_HELLO", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
newTld("hello", "HELLO", ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY))
.asBuilder()
.setInvoicingEnabled(true)
.build();
@@ -163,7 +163,7 @@ public class RegistryJpaReadTest {
}
private void setupForJoinQuery() {
Tld registry = newTld("com", "ABCD_APP");
Tld registry = newTld("com", "ABCDAPP");
Registrar registrar =
makeRegistrar1()
.asBuilder()
@@ -17,6 +17,7 @@ package google.registry.bsa;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.tldconfig.idn.IdnTableEnum.AUGMENTED_LATIN;
import static google.registry.tldconfig.idn.IdnTableEnum.EXTENDED_LATIN;
import static google.registry.tldconfig.idn.IdnTableEnum.JA;
import static google.registry.tldconfig.idn.IdnTableEnum.UNCONFUSABLE_LATIN;
@@ -43,6 +44,7 @@ public class IdnCheckerTest {
Tld jaonly;
Tld jandelatin;
Tld strictlatin;
Tld auglatin;
IdnChecker idnChecker;
@BeforeEach
@@ -50,6 +52,7 @@ public class IdnCheckerTest {
jaonly = createTld("jaonly");
jandelatin = createTld("jandelatin");
strictlatin = createTld("strictlatin");
auglatin = createTld("auglatin");
jaonly =
persistResource(
@@ -72,6 +75,13 @@ public class IdnCheckerTest {
.setBsaEnrollStartTime(Optional.of(fakeClock.nowUtc()))
.setIdnTables(ImmutableSet.of(UNCONFUSABLE_LATIN))
.build());
auglatin =
persistResource(
auglatin
.asBuilder()
.setBsaEnrollStartTime(Optional.of(fakeClock.nowUtc()))
.setIdnTables(ImmutableSet.of(AUGMENTED_LATIN))
.build());
fakeClock.advanceOneMilli();
idnChecker = new IdnChecker(fakeClock);
}
@@ -79,12 +89,13 @@ public class IdnCheckerTest {
@Test
void getAllValidIdns_allTlds() {
assertThat(idnChecker.getAllValidIdns("all"))
.containsExactly(EXTENDED_LATIN, JA, UNCONFUSABLE_LATIN);
.containsExactly(EXTENDED_LATIN, JA, UNCONFUSABLE_LATIN, AUGMENTED_LATIN);
}
@Test
void getAllValidIdns_notJa() {
assertThat(idnChecker.getAllValidIdns("à")).containsExactly(EXTENDED_LATIN, UNCONFUSABLE_LATIN);
assertThat(idnChecker.getAllValidIdns("à"))
.containsExactly(EXTENDED_LATIN, UNCONFUSABLE_LATIN, AUGMENTED_LATIN);
}
@Test
@@ -116,6 +127,7 @@ public class IdnCheckerTest {
@Test
void getForbiddingTlds_success() {
assertThat(idnChecker.getForbiddingTlds(ImmutableSet.of("JA"))).containsExactly(strictlatin);
assertThat(idnChecker.getForbiddingTlds(ImmutableSet.of("JA")))
.containsExactly(strictlatin, auglatin);
}
}
@@ -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())));
@@ -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;
@@ -289,6 +290,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);
@@ -77,9 +77,11 @@ import google.registry.model.billing.BillingRecurrence;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.domain.feestdv1.FeeCheckCommandExtensionStdV1.MultipleCurrenciesCannotBeCheckedException;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.registrar.Registrar;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.tld.Tld;
@@ -788,6 +790,31 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testSuccess_multipleCurencies_v12() throws Exception {
persistResource(
createTld("example")
.asBuilder()
.setCurrency(JPY)
.setCreateBillingCostTransitions(
ImmutableSortedMap.of(START_OF_TIME, Money.ofMajor(JPY, 800)))
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.ofMajor(JPY, 800)))
.setRenewBillingCostTransitions(
ImmutableSortedMap.of(START_OF_TIME, Money.ofMajor(JPY, 800)))
.setRegistryLockOrUnlockBillingCost(Money.ofMajor(JPY, 800))
.setServerStatusChangeBillingCost(Money.ofMajor(JPY, 800))
.setRestoreBillingCost(Money.ofMajor(JPY, 800))
.build());
persistResource(
Registrar.loadByRegistrarId("TheRegistrar")
.get()
.asBuilder()
.setBillingAccountMap(ImmutableMap.of(USD, "foo", JPY, "bar"))
.build());
setEppInput("domain_check_fee_multiple_currencies_v12.xml");
runFlowAssertResponse(loadFile("domain_check_fee_multiple_currencies_response_v12.xml"));
}
@Test
void testSuccess_superuserNotAuthorizedForTld() throws Exception {
persistActiveDomain("example2.tld");
@@ -951,23 +978,34 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_response_default_token_v11.xml"));
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_latest(String name, FeeFileLoader fileLoader) throws Exception {
@Test
void testFeeExtension_v12() throws Exception {
persistActiveDomain("example1.tld");
setEppInputXml(fileLoader.load(this, "domain_check_fee_v12.xml"));
runFlowAssertResponse(fileLoader.load(this, "domain_check_fee_response_v12.xml"));
setEppInput("domain_check_fee_v12.xml");
runFlowAssertResponse(loadFile("domain_check_fee_response_v12.xml"));
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_defaultToken_latest(String name, FeeFileLoader fileLoader)
throws Exception {
@Test
void testFeeExtension_stdv1() throws Exception {
persistActiveDomain("example1.tld");
setEppInput("domain_check_fee_stdv1.xml");
runFlowAssertResponse(loadFile("domain_check_fee_response_stdv1.xml"));
}
@Test
void testFeeExtension_defaultToken_v12() throws Exception {
setUpDefaultToken();
persistActiveDomain("example1.tld");
setEppInputXml(
fileLoader.load(this, "domain_check_fee_v12.xml", ImmutableMap.of("CURRENCY", "USD")));
runFlowAssertResponse(fileLoader.load(this, "domain_check_fee_response_default_token_v12.xml"));
setEppInput("domain_check_fee_v12.xml", ImmutableMap.of("CURRENCY", "USD"));
runFlowAssertResponse(loadFile("domain_check_fee_response_default_token_v12.xml"));
}
@Test
void testFeeExtension_defaultToken_stdv1() throws Exception {
setUpDefaultToken();
persistActiveDomain("example1.tld");
setEppInput("domain_check_fee_stdv1.xml", ImmutableMap.of("CURRENCY", "USD"));
runFlowAssertResponse(loadFile("domain_check_fee_response_default_token_stdv1.xml"));
}
@Test
@@ -1017,18 +1055,20 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
// Version 11 cannot have multiple commands.
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_multipleCommands_latest(String name, FeeFileLoader loader)
throws Exception {
setEppInputXml(loader.load(this, "domain_check_fee_multiple_commands_v12.xml"));
runFlowAssertResponse(loader.load(this, "domain_check_fee_multiple_commands_response_v12.xml"));
@Test
void testFeeExtension_multipleCommands_v12() throws Exception {
setEppInput("domain_check_fee_multiple_commands_v12.xml");
runFlowAssertResponse(loadFile("domain_check_fee_multiple_commands_response_v12.xml"));
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_multipleCommands_tokenNotValidForSome_latest(
String name, FeeFileLoader loader) throws Exception {
@Test
void testFeeExtension_multipleCommands_std_v1() throws Exception {
setEppInput("domain_check_fee_multiple_commands_stdv1.xml");
runFlowAssertResponse(loadFile("domain_check_fee_multiple_commands_response_stdv1.xml"));
}
@Test
void testFeeExtension_multipleCommands_tokenNotValidForSome_v12() throws Exception {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
@@ -1036,19 +1076,39 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE, CommandName.TRANSFER))
.setDiscountFraction(0.1)
.build());
setEppInputXml(loader.load(this, "domain_check_fee_multiple_commands_allocationtoken_v12.xml"));
setEppInput("domain_check_fee_multiple_commands_allocationtoken_v12.xml");
runFlowAssertResponse(
loader.load(this, "domain_check_fee_multiple_commands_allocationtoken_response_v12.xml"));
loadFile("domain_check_fee_multiple_commands_allocationtoken_response_v12.xml"));
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_multipleCommands_defaultTokenOnlyOnCreate_latest(
String name, FeeFileLoader loader) throws Exception {
setUpDefaultToken();
setEppInputXml(loader.load(this, "domain_check_fee_multiple_commands_v12.xml"));
@Test
void testFeeExtension_multipleCommands_tokenNotValidForSome_std_v1() throws Exception {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(UNLIMITED_USE)
.setAllowedEppActions(ImmutableSet.of(CommandName.CREATE, CommandName.TRANSFER))
.setDiscountFraction(0.1)
.build());
setEppInput("domain_check_fee_multiple_commands_allocationtoken_stdv1.xml");
runFlowAssertResponse(
loader.load(this, "domain_check_fee_multiple_commands_default_token_response_v12.xml"));
loadFile("domain_check_fee_multiple_commands_allocationtoken_response_stdv1.xml"));
}
@Test
void testFeeExtension_multipleCommands_defaultTokenOnlyOnCreate_v12() throws Exception {
setUpDefaultToken();
setEppInput("domain_check_fee_multiple_commands_v12.xml");
runFlowAssertResponse(
loadFile("domain_check_fee_multiple_commands_default_token_response_v12.xml"));
}
@Test
void testFeeExtension_multipleCommands_defaultTokenOnlyOnCreate_std_v1() throws Exception {
setUpDefaultToken();
setEppInput("domain_check_fee_multiple_commands_stdv1.xml");
runFlowAssertResponse(
loadFile("domain_check_fee_multiple_commands_default_token_response_stdv1.xml"));
}
@Disabled("TODO(b/454680236): broken test")
@@ -1316,33 +1376,47 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v11_update.xml"));
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_premiumLabels_latest(String name, FeeFileLoader loader) throws Exception {
@Test
void testFeeExtension_premiumLabels_v12() throws Exception {
createTld("example");
setEppInputXml(loader.load(this, "domain_check_fee_premium_v12.xml"));
runFlowAssertResponse(loader.load(this, "domain_check_fee_premium_response_v12.xml"));
setEppInput("domain_check_fee_premium_v12.xml");
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v12.xml"));
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_premiumLabels_v12_specifiedPriceRenewal_renewPriceOnly(
String name, FeeFileLoader loader) throws Exception {
@Test
void testFeeExtension_premiumLabels_std_v1() throws Exception {
createTld("example");
setEppInput("domain_check_fee_premium_stdv1.xml");
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_stdv1.xml"));
}
@Test
void testFeeExtension_premiumLabels_v12_specifiedPriceRenewal_renewPriceOnly() throws Exception {
createTld("example");
persistBillingRecurrenceForDomain(
persistActiveDomain("rich.example"), SPECIFIED, Money.of(USD, new BigDecimal("27.74")));
setEppInputXml(loader.load(this, "domain_check_fee_premium_v12_renew_only.xml"));
setEppInput("domain_check_fee_premium_v12_renew_only.xml");
runFlowAssertResponse(
loader.load(
this,
loadFile(
"domain_check_fee_premium_response_v12_renew_only.xml",
ImmutableMap.of("RENEWPRICE", "27.74")));
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_premiumLabels_doesNotApplyDefaultToken_latest(
String name, FeeFileLoader loader) throws Exception {
@Test
void testFeeExtension_premiumLabels_std_v1_specifiedPriceRenewal_renewPriceOnly()
throws Exception {
createTld("example");
persistBillingRecurrenceForDomain(
persistActiveDomain("rich.example"), SPECIFIED, Money.of(USD, new BigDecimal("27.74")));
setEppInput("domain_check_fee_premium_stdv1_renew_only.xml");
runFlowAssertResponse(
loadFile(
"domain_check_fee_premium_response_stdv1_renew_only.xml",
ImmutableMap.of("RENEWPRICE", "27.74")));
}
@Test
void testFeeExtension_premiumLabels_doesNotApplyDefaultToken_v12() throws Exception {
createTld("example");
AllocationToken defaultToken =
persistResource(
@@ -1359,19 +1433,46 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.build());
setEppInputXml(loader.load(this, "domain_check_fee_premium_v12.xml"));
runFlowAssertResponse(loader.load(this, "domain_check_fee_premium_response_v12.xml"));
setEppInput("domain_check_fee_premium_v12.xml");
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v12.xml"));
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_premiumLabels_v12_withRenewalOnRestore(String name, FeeFileLoader loader)
throws Exception {
@Test
void testFeeExtension_premiumLabels_doesNotApplyDefaultToken_std_v1() throws Exception {
createTld("example");
setEppInputXml(loader.load(this, "domain_check_fee_premium_v12.xml"));
AllocationToken defaultToken =
persistResource(
new AllocationToken.Builder()
.setToken("bbbbb")
.setTokenType(DEFAULT_PROMO)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("example"))
.setDiscountPremiums(false)
.setDiscountFraction(0.5)
.build());
persistResource(
Tld.get("example")
.asBuilder()
.setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey()))
.build());
setEppInput("domain_check_fee_premium_stdv1.xml");
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_stdv1.xml"));
}
@Test
void testFeeExtension_premiumLabels_v12_withRenewalOnRestore() throws Exception {
createTld("example");
setEppInput("domain_check_fee_premium_v12.xml");
persistPendingDeleteDomain("rich.example");
runFlowAssertResponse(
loader.load(this, "domain_check_fee_premium_response_v12_with_renewal.xml"));
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v12_with_renewal.xml"));
}
@Test
void testFeeExtension_premiumLabels_std_v1_withRenewalOnRestore() throws Exception {
createTld("example");
setEppInput("domain_check_fee_premium_stdv1.xml");
persistPendingDeleteDomain("rich.example");
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_stdv1_with_renewal.xml"));
}
@Test
@@ -1482,23 +1583,32 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
loadFile("domain_check_fee_reserved_response_v11_restore_with_renewals.xml"));
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_reservedName_latest(String name, FeeFileLoader loader) throws Exception {
@Test
void testFeeExtension_reservedName_v12() throws Exception {
persistResource(
Tld.get("tld")
.asBuilder()
.setReservedLists(createReservedList())
.setPremiumList(persistPremiumList("tld", USD, "premiumcollision,USD 70"))
.build());
setEppInputXml(loader.load(this, "domain_check_fee_reserved_v12.xml"));
runFlowAssertResponse(loader.load(this, "domain_check_fee_reserved_response_v12.xml"));
setEppInput("domain_check_fee_reserved_v12.xml");
runFlowAssertResponse(loadFile("domain_check_fee_reserved_response_v12.xml"));
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_reservedName_restoreFeeWithDupes_latest(String name, FeeFileLoader loader)
throws Exception {
@Test
void testFeeExtension_reservedName_std_v1() throws Exception {
persistResource(
Tld.get("tld")
.asBuilder()
.setReservedLists(createReservedList())
.setPremiumList(persistPremiumList("tld", USD, "premiumcollision,USD 70"))
.build());
setEppInput("domain_check_fee_reserved_stdv1.xml");
runFlowAssertResponse(loadFile("domain_check_fee_reserved_response_stdv1.xml"));
}
@Test
void testFeeExtension_reservedName_restoreFeeWithDupes_v12() throws Exception {
persistResource(
Tld.get("tld")
.asBuilder()
@@ -1506,9 +1616,23 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
.setPremiumList(persistPremiumList("tld", USD, "premiumcollision,USD 70"))
.build());
// The domain needs to exist in order for it to be loaded to check for restore fee.
setEppInputXml(loader.load(this, "domain_check_fee_reserved_dupes_v12.xml"));
setEppInput("domain_check_fee_reserved_dupes_v12.xml");
persistBillingRecurrenceForDomain(persistActiveDomain("allowedinsunrise.tld"), DEFAULT, null);
runFlowAssertResponse(loader.load(this, "domain_check_fee_reserved_dupes_response_v12.xml"));
runFlowAssertResponse(loadFile("domain_check_fee_reserved_dupes_response_v12.xml"));
}
@Test
void testFeeExtension_reservedName_restoreFeeWithDupes_std_v1() throws Exception {
persistResource(
Tld.get("tld")
.asBuilder()
.setReservedLists(createReservedList())
.setPremiumList(persistPremiumList("tld", USD, "premiumcollision,USD 70"))
.build());
// The domain needs to exist in order for it to be loaded to check for restore fee.
setEppInput("domain_check_fee_reserved_dupes_stdv1.xml");
persistBillingRecurrenceForDomain(persistActiveDomain("allowedinsunrise.tld"), DEFAULT, null);
runFlowAssertResponse(loadFile("domain_check_fee_reserved_dupes_response_stdv1.xml"));
}
@Test
@@ -1595,10 +1719,8 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_reserved_sunrise_response_v11_restore.xml"));
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_feesNotOmittedOnReservedNamesInSunrise_latest(
String name, FeeFileLoader loader) throws Exception {
@Test
void testFeeExtension_feesNotOmittedOnReservedNamesInSunrise_v12() throws Exception {
createTld("tld", START_DATE_SUNRISE);
persistResource(
Tld.get("tld")
@@ -1606,8 +1728,21 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
.setReservedLists(createReservedList())
.setPremiumList(persistPremiumList("tld", USD, "premiumcollision,USD 70"))
.build());
setEppInputXml(loader.load(this, "domain_check_fee_reserved_v12.xml"));
runFlowAssertResponse(loader.load(this, "domain_check_fee_reserved_sunrise_response_v12.xml"));
setEppInput("domain_check_fee_reserved_v12.xml");
runFlowAssertResponse(loadFile("domain_check_fee_reserved_sunrise_response_v12.xml"));
}
@Test
void testFeeExtension_feesNotOmittedOnReservedNamesInSunrise_std_v1() throws Exception {
createTld("tld", START_DATE_SUNRISE);
persistResource(
Tld.get("tld")
.asBuilder()
.setReservedLists(createReservedList())
.setPremiumList(persistPremiumList("tld", USD, "premiumcollision,USD 70"))
.build());
setEppInput("domain_check_fee_reserved_stdv1.xml");
runFlowAssertResponse(loadFile("domain_check_fee_reserved_sunrise_response_stdv1.xml"));
}
@Test
@@ -1794,14 +1929,47 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@ParameterizedTest
@MethodSource("provideFeeTestParams")
void testFeeExtension_invalidCommand_latest(String name, FeeFileLoader loader) {
setEppInputXml(loader.load(this, "domain_check_fee_invalid_command_v12.xml"));
@Test
void testFeeExtension_invalidCommand_v12() {
setEppInput("domain_check_fee_invalid_command_v12.xml");
EppException thrown = assertThrows(UnknownFeeCommandException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFeeExtension_invalidCommand_std_v1() {
setEppInput("domain_check_fee_invalid_command_stdv1.xml");
EppException thrown = assertThrows(UnknownFeeCommandException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFeeExtension_invalid_multipleCurrencies_std_v1() {
persistResource(
createTld("example")
.asBuilder()
.setCurrency(JPY)
.setCreateBillingCostTransitions(
ImmutableSortedMap.of(START_OF_TIME, Money.ofMajor(JPY, 800)))
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.ofMajor(JPY, 800)))
.setRenewBillingCostTransitions(
ImmutableSortedMap.of(START_OF_TIME, Money.ofMajor(JPY, 800)))
.setRegistryLockOrUnlockBillingCost(Money.ofMajor(JPY, 800))
.setServerStatusChangeBillingCost(Money.ofMajor(JPY, 800))
.setRestoreBillingCost(Money.ofMajor(JPY, 800))
.build());
persistResource(
Registrar.loadByRegistrarId("TheRegistrar")
.get()
.asBuilder()
.setBillingAccountMap(ImmutableMap.of(USD, "foo", JPY, "bar"))
.build());
setEppInput("domain_check_fee_multiple_currencies_stdv1.xml");
assertAboutEppExceptions()
.that(assertThrows(MultipleCurrenciesCannotBeCheckedException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testSuccess_eapFeeCheck_v06() throws Exception {
runEapFeeCheckTest(
@@ -1834,21 +2002,11 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
new FeeFileLoader(/* isFeeStdV1= */ false));
}
@Disabled("v1.0 buggy")
@Test
void testSuccess_eapFeeCheck_std_v1() throws Exception {
runEapFeeCheckTest(
"domain_check_fee_v12.xml",
"domain_check_eap_fee_response_v12.xml",
new FeeFileLoader(/* isFeeStdV1= */ true));
}
@Disabled("v1.0 buggy")
@Test
void testSuccess_eapFeeCheck_date_std_v1() throws Exception {
runEapFeeCheckTest(
"domain_check_fee_date_v12.xml",
"domain_check_eap_fee_response_date_v12.xml",
"domain_check_fee_stdv1.xml",
"domain_check_eap_fee_response_stdv1.xml",
new FeeFileLoader(/* isFeeStdV1= */ true));
}
@@ -2003,10 +2161,14 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
}
}
/**
* Fee versions 0.12 and 1.0 are the same in how they're requested (though not in the responses.
* As a result, for tests that throw an exception, we can use the same XML content.
*/
@SuppressWarnings("unused")
private static Stream<Arguments> provideFeeTestParams() {
return Stream.of(
// Arguments.of("fee_std_v1", new FeeFileLoader(true)),
Arguments.of("fee_std_v1", new FeeFileLoader(true)),
Arguments.of("fee_12", new FeeFileLoader(false)));
}
}
@@ -67,7 +67,9 @@ 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;
@@ -1373,6 +1375,30 @@ 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_removeNonexistentValue() 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_minimumDataset_addingNewRegistrantFails() throws Exception {
persistReferencedEntities();
@@ -20,7 +20,9 @@ import static google.registry.testing.DatabaseHelper.persistDeletedHost;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap;
import google.registry.flows.EppException;
import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
import google.registry.flows.FlowUtils.NotLoggedInException;
import google.registry.flows.ResourceCheckFlowTestCase;
import google.registry.flows.exceptions.TooManyResourceChecksException;
@@ -95,4 +97,36 @@ class HostCheckFlowTest extends ResourceCheckFlowTestCase<HostCheckFlow, Host> {
runFlow();
assertIcannReportingActivityFieldLogged("srs-host-check");
}
@Test
void testFailure_dotHost() throws Exception {
setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", ".host"));
assertAboutEppExceptions()
.that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testFailure_dashHost() {
setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", "-host"));
assertAboutEppExceptions()
.that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testFailure_underscoreHost() {
setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", "_host"));
assertAboutEppExceptions()
.that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testFailure_hostDash() {
setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", "host-"));
assertAboutEppExceptions()
.that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow))
.marshalsToXml();
}
}
@@ -39,12 +39,13 @@ import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientExcept
import google.registry.flows.exceptions.ResourceCreateContentionException;
import google.registry.flows.host.HostCreateFlow.SubordinateHostMustHaveIpException;
import google.registry.flows.host.HostCreateFlow.UnexpectedExternalHostIpException;
import google.registry.flows.host.HostFlowUtils.BadHostNameCharacterException;
import google.registry.flows.host.HostFlowUtils.HostNameNotLowerCaseException;
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;
import google.registry.model.ForeignKeyUtils;
@@ -286,7 +287,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, Host> {
@Test
void testFailure_badCharacter() {
doFailingHostNameTest("foo bar", InvalidHostNameException.class);
doFailingHostNameTest("foo bar", BadHostNameCharacterException.class);
}
@Test
@@ -322,6 +323,26 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, Host> {
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailure_localhostInetAddress_ipv4() {
createTld("tld");
persistActiveDomain("example.tld");
setEppHostCreateInput("ns1.example.tld", "<host:addr ip=\"v4\">127.0.0.1</host:addr>");
assertAboutEppExceptions()
.that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testFailure_localhostInetAddress_ipv6() {
createTld("tld");
persistActiveDomain("example.tld");
setEppHostCreateInput("ns1.example.tld", "<host:addr ip=\"v6\">::1</host:addr>");
assertAboutEppExceptions()
.that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testIcannActivityReportField_getsLogged() throws Exception {
runFlow();
@@ -48,19 +48,22 @@ 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;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
import google.registry.flows.host.HostFlowUtils.BadHostNameCharacterException;
import google.registry.flows.host.HostFlowUtils.HostDomainNotOwnedException;
import google.registry.flows.host.HostFlowUtils.HostNameNotLowerCaseException;
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;
import google.registry.flows.host.HostUpdateFlow.CannotAddIpToExternalHostException;
@@ -521,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();
@@ -560,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());
@@ -597,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();
@@ -631,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();
@@ -665,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();
@@ -686,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
@@ -727,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
@@ -762,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(
@@ -1006,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");
@@ -1077,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(
@@ -1259,7 +1330,7 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
@Test
void testFailure_renameToBadCharacter() throws Exception {
doFailingHostNameTest("foo bar", InvalidHostNameException.class);
doFailingHostNameTest("foo bar", BadHostNameCharacterException.class);
}
@Test
@@ -1306,6 +1377,28 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
doFailingHostNameTest("foo.co.uk", HostNameTooShallowException.class);
}
@Test
void testFailure_localhostInetAddress_ipv4() throws Exception {
createTld("tld");
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
setEppHostUpdateInput(
"ns1.example.tld", "ns2.example.tld", "<host:addr ip=\"v4\">127.0.0.1</host:addr>", null);
assertAboutEppExceptions()
.that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testFailure_localhostInetAddress_ipv6() throws Exception {
createTld("tld");
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
setEppHostUpdateInput(
"ns1.example.tld", "ns2.example.tld", "<host:addr ip=\"v6\">::1</host:addr>", null);
assertAboutEppExceptions()
.that(assertThrows(LoopbackIpNotValidForHostException.class, this::runFlow))
.marshalsToXml();
}
@Test
void testSuccess_metadata() throws Exception {
createTld("tld");
@@ -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();
}
}
}
@@ -751,7 +751,9 @@ public final class TldTest extends EntityTestCase {
assertThrows(
IllegalArgumentException.class,
() -> Tld.get("tld").asBuilder().setRoidSuffix("123456789"));
assertThat(e).hasMessageThat().isEqualTo("ROID suffix must be in format ^[A-Z\\d_]{1,8}$");
assertThat(e)
.hasMessageThat()
.isEqualTo("ROID suffix 123456789 must be in format ^[A-Z\\d]{1,8}$");
}
@Test
@@ -766,6 +768,12 @@ public final class TldTest extends EntityTestCase {
IllegalArgumentException.class, () -> Tld.get("tld").asBuilder().setRoidSuffix("ABC-DEF"));
}
@Test
void testFailure_roidSuffixContainsUnderscores() {
assertThrows(
IllegalArgumentException.class, () -> Tld.get("tld").asBuilder().setRoidSuffix("ABC_DEF"));
}
@Test
void testSuccess_setDefaultPromoTokens() {
Tld registry = Tld.get("tld");
@@ -28,6 +28,7 @@ import google.registry.flows.TlsCredentials.EppTlsModule;
import google.registry.flows.custom.CustomLogicModule;
import google.registry.loadtest.LoadTestModule;
import google.registry.monitoring.whitebox.WhiteboxModule;
import google.registry.mosapi.module.MosApiRequestModule;
import google.registry.rdap.RdapModule;
import google.registry.rde.RdeModule;
import google.registry.reporting.ReportingModule;
@@ -60,6 +61,7 @@ import google.registry.ui.server.console.ConsoleModule;
EppToolModule.class,
IcannReportingModule.class,
LoadTestModule.class,
MosApiRequestModule.class,
RdapModule.class,
RdeModule.class,
ReportingModule.class,
@@ -0,0 +1,98 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.net.MediaType;
import com.google.gson.Gson;
import google.registry.mosapi.MosApiModels.AllServicesStateResponse;
import google.registry.mosapi.MosApiModels.ServiceStateSummary;
import google.registry.request.HttpException.ServiceUnavailableException;
import google.registry.testing.FakeResponse;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/** Unit tests for {@link GetServiceStateAction}. */
@ExtendWith(MockitoExtension.class)
public class GetServiceStateActionTest {
@Mock private MosApiStateService stateService;
private final FakeResponse response = new FakeResponse();
private final Gson gson = new Gson();
@Test
void testRun_singleTld_returnsStateForTld() throws Exception {
GetServiceStateAction action =
new GetServiceStateAction(stateService, response, gson, Optional.of("example"));
ServiceStateSummary summary = new ServiceStateSummary("example", "Up", null);
when(stateService.getServiceStateSummary("example")).thenReturn(summary);
action.run();
assertThat(response.getContentType()).isEqualTo(MediaType.JSON_UTF_8);
assertThat(response.getPayload())
.contains(
"""
"overallStatus":"Up"
"""
.trim());
verify(stateService).getServiceStateSummary("example");
}
@Test
void testRun_noTld_returnsStateForAll() {
GetServiceStateAction action =
new GetServiceStateAction(stateService, response, gson, Optional.empty());
AllServicesStateResponse allStates = new AllServicesStateResponse(ImmutableList.of());
when(stateService.getAllServiceStateSummaries()).thenReturn(allStates);
action.run();
assertThat(response.getContentType()).isEqualTo(MediaType.JSON_UTF_8);
assertThat(response.getPayload())
.contains(
"""
"serviceStates":[]
"""
.trim());
verify(stateService).getAllServiceStateSummaries();
}
@Test
void testRun_serviceThrowsException_throwsServiceUnavailable() throws Exception {
GetServiceStateAction action =
new GetServiceStateAction(stateService, response, gson, Optional.of("example"));
doThrow(new MosApiException("Backend error", null))
.when(stateService)
.getServiceStateSummary("example");
ServiceUnavailableException thrown =
assertThrows(ServiceUnavailableException.class, action::run);
assertThat(thrown).hasMessageThat().isEqualTo("Error fetching MoSAPI service state.");
}
}
@@ -11,7 +11,7 @@
// 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.model;
package google.registry.mosapi;
import static com.google.common.truth.Truth.assertThat;
@@ -20,7 +20,6 @@ import google.registry.mosapi.MosApiException.DateOrderInvalidException;
import google.registry.mosapi.MosApiException.EndDateSyntaxInvalidException;
import google.registry.mosapi.MosApiException.MosApiAuthorizationException;
import google.registry.mosapi.MosApiException.StartDateSyntaxInvalidException;
import google.registry.mosapi.model.MosApiErrorResponse;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link MosApiException}. */
@@ -0,0 +1,172 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import google.registry.mosapi.MosApiModels.AllServicesStateResponse;
import google.registry.mosapi.MosApiModels.IncidentSummary;
import google.registry.mosapi.MosApiModels.ServiceStateSummary;
import google.registry.mosapi.MosApiModels.ServiceStatus;
import google.registry.mosapi.MosApiModels.TldServiceState;
import org.junit.Test;
/** Tests for {@link MosApiModels}. */
public final class MosApiModelsTest {
private static final Gson gson =
new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
@Test
public void testAllServicesStateResponse_nullCollection_initializedToEmpty() {
AllServicesStateResponse response = new AllServicesStateResponse(null);
assertThat(response.serviceStates()).isEmpty();
assertThat(response.serviceStates()).isNotNull();
}
@Test
public void testServiceStateSummary_nullCollection_initializedToEmpty() {
ServiceStateSummary summary = new ServiceStateSummary("example", "Up", null);
assertThat(summary.activeIncidents()).isEmpty();
assertThat(summary.activeIncidents()).isNotNull();
}
@Test
public void testServiceStatus_nullCollection_initializedToEmpty() {
ServiceStatus status = new ServiceStatus("Up", 0.0, null);
assertThat(status.incidents()).isEmpty();
assertThat(status.incidents()).isNotNull();
}
@Test
public void testTldServiceState_nullCollection_initializedToEmpty() {
TldServiceState state = new TldServiceState("example", 123456L, "Up", null);
assertThat(state.serviceStatuses()).isEmpty();
assertThat(state.serviceStatuses()).isNotNull();
}
@Test
public void testIncidentSummary_jsonSerialization() {
IncidentSummary incident = new IncidentSummary("inc-123", 1000L, false, "Active", 2000L);
String json = gson.toJson(incident);
// Using Text Blocks to avoid escaping quotes
assertThat(json)
.contains(
"""
"incidentID":"inc-123"
"""
.trim());
assertThat(json)
.contains(
"""
"startTime":1000
"""
.trim());
assertThat(json)
.contains(
"""
"falsePositive":false
"""
.trim());
assertThat(json)
.contains(
"""
"state":"Active"
"""
.trim());
assertThat(json)
.contains(
"""
"endTime":2000
"""
.trim());
}
@Test
public void testServiceStatus_jsonSerialization() {
IncidentSummary incident = new IncidentSummary("inc-1", 1000L, false, "Resolved", null);
ServiceStatus status = new ServiceStatus("Down", 75.5, ImmutableList.of(incident));
String json = gson.toJson(status);
assertThat(json)
.contains(
"""
"status":"Down"
"""
.trim());
assertThat(json)
.contains(
"""
"emergencyThreshold":75.5
"""
.trim());
assertThat(json)
.contains(
"""
"incidents":[
"""
.trim());
}
@Test
public void testTldServiceState_jsonSerialization() {
ServiceStatus dnsStatus = new ServiceStatus("Up", 0.0, ImmutableList.of());
TldServiceState state =
new TldServiceState("app", 1700000000L, "Up", ImmutableMap.of("DNS", dnsStatus));
String json = gson.toJson(state);
assertThat(json)
.contains(
"""
"tld":"app"
"""
.trim());
assertThat(json)
.contains(
"""
"status":"Up"
"""
.trim());
assertThat(json)
.contains(
"""
"testedServices":{"DNS":{
"""
.trim());
}
@Test
public void testAllServicesStateResponse_jsonSerialization() {
ServiceStateSummary summary = new ServiceStateSummary("dev", "Up", ImmutableList.of());
AllServicesStateResponse response = new AllServicesStateResponse(ImmutableList.of(summary));
String json = gson.toJson(response);
assertThat(json)
.contains(
"""
"serviceStates":[
"""
.trim());
assertThat(json)
.contains(
"""
"tld":"dev"
"""
.trim());
}
}
@@ -0,0 +1,172 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.MoreExecutors;
import google.registry.mosapi.MosApiModels.AllServicesStateResponse;
import google.registry.mosapi.MosApiModels.IncidentSummary;
import google.registry.mosapi.MosApiModels.ServiceStateSummary;
import google.registry.mosapi.MosApiModels.ServiceStatus;
import google.registry.mosapi.MosApiModels.TldServiceState;
import java.util.concurrent.ExecutorService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/** Unit tests for {@link MosApiStateService}. */
@ExtendWith(MockitoExtension.class)
class MosApiStateServiceTest {
@Mock private ServiceMonitoringClient client;
@Mock private MosApiMetrics metrics;
private final ExecutorService executor = MoreExecutors.newDirectExecutorService();
private MosApiStateService service;
@BeforeEach
void setUp() {
service = new MosApiStateService(client, metrics, ImmutableSet.of("tld1", "tld2"), executor);
}
@Test
void testGetServiceStateSummary_upStatus_returnsEmptyIncidents() throws Exception {
TldServiceState rawState = new TldServiceState("tld1", 12345L, "Up", ImmutableMap.of());
when(client.getTldServiceState("tld1")).thenReturn(rawState);
ServiceStateSummary result = service.getServiceStateSummary("tld1");
assertThat(result.tld()).isEqualTo("tld1");
assertThat(result.overallStatus()).isEqualTo("Up");
assertThat(result.activeIncidents()).isEmpty();
}
@Test
void testGetServiceStateSummary_downStatus_filtersActiveIncidents() throws Exception {
IncidentSummary dnsIncident = new IncidentSummary("inc-1", 100L, false, "Open", null);
ServiceStatus dnsService = new ServiceStatus("Down", 50.0, ImmutableList.of(dnsIncident));
ServiceStatus rdapService = new ServiceStatus("Up", 0.0, ImmutableList.of());
TldServiceState rawState =
new TldServiceState(
"tld1", 12345L, "Down", ImmutableMap.of("DNS", dnsService, "RDAP", rdapService));
when(client.getTldServiceState("tld1")).thenReturn(rawState);
ServiceStateSummary result = service.getServiceStateSummary("tld1");
assertThat(result.overallStatus()).isEqualTo("Down");
assertThat(result.activeIncidents()).hasSize(1);
ServiceStatus incidentSummary = result.activeIncidents().get(0);
assertThat(incidentSummary.status()).isEqualTo("DNS");
assertThat(incidentSummary.incidents()).containsExactly(dnsIncident);
}
@Test
void testGetServiceStateSummary_throwsException_whenClientFails() throws Exception {
when(client.getTldServiceState("tld1")).thenThrow(new MosApiException("Network error", null));
assertThrows(MosApiException.class, () -> service.getServiceStateSummary("tld1"));
}
@Test
void testGetAllServiceStateSummaries_success() throws Exception {
TldServiceState state1 = new TldServiceState("tld1", 1L, "Up", ImmutableMap.of());
TldServiceState state2 = new TldServiceState("tld2", 2L, "Up", ImmutableMap.of());
when(client.getTldServiceState("tld1")).thenReturn(state1);
when(client.getTldServiceState("tld2")).thenReturn(state2);
AllServicesStateResponse response = service.getAllServiceStateSummaries();
assertThat(response.serviceStates()).hasSize(2);
assertThat(response.serviceStates().stream().map(ServiceStateSummary::tld))
.containsExactly("tld1", "tld2");
}
@Test
void testGetAllServiceStateSummaries_partialFailure_returnsErrorState() throws Exception {
TldServiceState state1 = new TldServiceState("tld1", 1L, "Up", ImmutableMap.of());
when(client.getTldServiceState("tld1")).thenReturn(state1);
when(client.getTldServiceState("tld2")).thenThrow(new MosApiException("Failure", null));
AllServicesStateResponse response = service.getAllServiceStateSummaries();
assertThat(response.serviceStates()).hasSize(2);
ServiceStateSummary summary1 =
response.serviceStates().stream().filter(s -> s.tld().equals("tld1")).findFirst().get();
assertThat(summary1.overallStatus()).isEqualTo("Up");
ServiceStateSummary summary2 =
response.serviceStates().stream().filter(s -> s.tld().equals("tld2")).findFirst().get();
assertThat(summary2.overallStatus()).isEqualTo("ERROR");
assertThat(summary2.activeIncidents()).isEmpty();
}
@Test
void testTriggerMetricsForAllServiceStateSummaries_success() throws Exception {
TldServiceState state1 = new TldServiceState("tld1", 1L, "Up", ImmutableMap.of());
TldServiceState state2 = new TldServiceState("tld2", 2L, "Up", ImmutableMap.of());
when(client.getTldServiceState("tld1")).thenReturn(state1);
when(client.getTldServiceState("tld2")).thenReturn(state2);
service.triggerMetricsForAllServiceStateSummaries();
verify(metrics)
.recordStates(
argThat(
states ->
states.size() == 2
&& states.stream()
.anyMatch(s -> s.tld().equals("tld1") && s.status().equals("Up"))
&& states.stream()
.anyMatch(s -> s.tld().equals("tld2") && s.status().equals("Up"))));
}
@Test
void testTriggerMetricsForAllServiceStateSummaries_partialFailure_recordsErrorMetric()
throws Exception {
TldServiceState state1 = new TldServiceState("tld1", 1L, "Up", ImmutableMap.of());
when(client.getTldServiceState("tld1")).thenReturn(state1);
when(client.getTldServiceState("tld2")).thenThrow(new MosApiException("Network Error", null));
service.triggerMetricsForAllServiceStateSummaries();
verify(metrics)
.recordStates(
argThat(
states ->
states.size() == 1
&& states.stream()
.anyMatch(s -> s.tld().equals("tld1") && s.status().equals("Up"))));
}
}
@@ -0,0 +1,140 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.gson.Gson;
import google.registry.mosapi.MosApiModels.TldServiceState;
import google.registry.tools.GsonUtils;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class ServiceMonitoringClientTest {
private static final String TLD = "example";
private static final String ENDPOINT = "v2/monitoring/state";
private final MosApiClient mosApiClient = mock(MosApiClient.class);
private final Gson gson = GsonUtils.provideGson();
private ServiceMonitoringClient client;
@BeforeEach
void beforeEach() {
client = new ServiceMonitoringClient(mosApiClient, gson);
}
@Test
void testGetTldServiceState_success() throws Exception {
String jsonResponse =
"""
{
"tld": "example",
"services": [
{
"service": "DNS",
"status": "OPERATIONAL"
}
]
}
""";
try (Response response = createMockResponse(200, jsonResponse)) {
when(mosApiClient.sendGetRequest(eq(TLD), eq(ENDPOINT), anyMap(), anyMap()))
.thenReturn(response);
TldServiceState result = client.getTldServiceState(TLD);
assertThat(gson.toJson(result)).contains("example");
}
}
@Test
void testGetTldServiceState_apiError_throwsMosApiException() throws Exception {
String errorJson =
"""
{
"resultCode": "2011",
"message": "Invalid duration"
}
""";
try (Response response = createMockResponse(400, errorJson)) {
when(mosApiClient.sendGetRequest(eq(TLD), eq(ENDPOINT), anyMap(), anyMap()))
.thenReturn(response);
MosApiException thrown =
assertThrows(MosApiException.class, () -> client.getTldServiceState(TLD));
assertThat(thrown.getMessage()).contains("2011");
assertThat(thrown.getMessage()).contains("Invalid duration");
}
}
@Test
void testGetTldServiceState_nonJsonError_throwsMosApiException() throws Exception {
String htmlError =
"""
<html>
<body>502 Bad Gateway</body>
</html>
""";
try (Response response = createMockResponse(502, htmlError)) {
when(mosApiClient.sendGetRequest(eq(TLD), eq(ENDPOINT), anyMap(), anyMap()))
.thenReturn(response);
MosApiException thrown =
assertThrows(MosApiException.class, () -> client.getTldServiceState(TLD));
assertThat(thrown.getMessage()).contains("MoSAPI json parsing error (502)");
assertThat(thrown.getMessage()).contains("502 Bad Gateway");
}
}
@Test
void testGetTldServiceState_emptyBody_throwsMosApiException() throws Exception {
Response response =
new Response.Builder()
.request(new Request.Builder().url("http://localhost").build())
.protocol(Protocol.HTTP_1_1)
.code(204)
.message("No Content")
.build();
when(mosApiClient.sendGetRequest(eq(TLD), eq(ENDPOINT), anyMap(), anyMap()))
.thenReturn(response);
MosApiException thrown =
assertThrows(MosApiException.class, () -> client.getTldServiceState(TLD));
assertThat(thrown.getMessage()).contains("returned an empty body");
}
private Response createMockResponse(int code, String body) {
return new Response.Builder()
.request(new Request.Builder().url("http://localhost").build())
.protocol(Protocol.HTTP_1_1)
.code(code)
.message(code == 200 ? "OK" : "Error")
.body(ResponseBody.create(body, MediaType.parse("application/json")))
.build();
}
}
@@ -0,0 +1,69 @@
// 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.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import com.google.common.net.MediaType;
import google.registry.request.HttpException.InternalServerErrorException;
import google.registry.testing.FakeResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
/** Unit tests for {@link TriggerServiceStateActionTest}. */
@ExtendWith(MockitoExtension.class)
public class TriggerServiceStateActionTest {
private final MosApiStateService stateService = mock(MosApiStateService.class);
private final FakeResponse response = new FakeResponse();
private TriggerServiceStateAction action;
@BeforeEach
void beforeEach() {
action = new TriggerServiceStateAction(stateService, response);
}
@Test
void testRun_success() {
action.run();
verify(stateService).triggerMetricsForAllServiceStateSummaries();
assertThat(response.getContentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8);
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload())
.isEqualTo("MoSAPI metrics triggered successfully for all TLDs.");
}
@Test
void testRun_failure_throwsInternalServerError() {
doThrow(new RuntimeException("Database error"))
.when(stateService)
.triggerMetricsForAllServiceStateSummaries();
InternalServerErrorException thrown =
assertThrows(InternalServerErrorException.class, () -> action.run());
assertThat(thrown.getMessage()).contains("Failed to process MoSAPI metrics.");
assertThat(response.getContentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8);
}
}
@@ -0,0 +1,57 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.mosapi.module;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link MosApiRequestModule}. */
public class MosApiRequestModuleTest {
@Test
void testProvideTld_paramPresent() {
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getParameter("tld")).thenReturn("example.tld");
Optional<String> result = MosApiRequestModule.provideTld(req);
assertThat(result).hasValue("example.tld");
}
@Test
void testProvideTld_paramMissing() {
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getParameter("tld")).thenReturn(null);
Optional<String> result = MosApiRequestModule.provideTld(req);
assertThat(result).isEmpty();
}
@Test
void testProvideTld_paramEmptyString() {
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getParameter("tld")).thenReturn("");
Optional<String> result = MosApiRequestModule.provideTld(req);
assertThat(result).isEmpty();
}
}
@@ -246,7 +246,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
.isEqualTo(
addDomainBoilerplateNotices(
jsonFileBuilder()
.addDomain("cat.1.tld", "D-1_TLD")
.addDomain("cat.1.tld", "D-1TLD")
.addNameserver("ns1.cat.lol", "2-ROID")
.addNameserver("ns2.cat.lol", "4-ROID")
.addRegistrar("Multilevel Registrar")
@@ -732,7 +732,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
RequestType.NAME,
"cat.1.test",
jsonFileBuilder()
.addDomain("cat.1.test", "1B-1_TEST")
.addDomain("cat.1.test", "1B-1TEST")
.addRegistrar("1.test")
.addNameserver("ns1.cat.1.test", "17-ROID")
.addNameserver("ns2.cat.2.test", "19-ROID")
@@ -746,7 +746,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
RequestType.NAME,
"ca*.1.test",
jsonFileBuilder()
.addDomain("cat.1.test", "1B-1_TEST")
.addDomain("cat.1.test", "1B-1TEST")
.addRegistrar("1.test")
.addNameserver("ns1.cat.1.test", "17-ROID")
.addNameserver("ns2.cat.2.test", "19-ROID")
@@ -822,7 +822,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
.that(generateActualJson(RequestType.NAME, "cat.*"))
.isEqualTo(
jsonFileBuilder()
.addDomain("cat.1.test", "1B-1_TEST")
.addDomain("cat.1.test", "1B-1TEST")
.addDomain("cat.example", "F-EXAMPLE")
.addDomain("cat.lol", "6-LOL")
.addDomain("cat.みんな", "15-Q9JYB4C")
@@ -860,7 +860,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
.that(generateActualJson(RequestType.NAME, "cat*"))
.isEqualTo(
jsonFileBuilder()
.addDomain("cat.1.test", "1B-1_TEST")
.addDomain("cat.1.test", "1B-1TEST")
.addDomain("cat.example", "F-EXAMPLE")
.addDomain("cat.lol", "6-LOL")
.addDomain("cat.みんな", "15-Q9JYB4C")
@@ -1284,7 +1284,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
RequestType.NS_LDH_NAME,
"ns1.cat.1.test",
jsonFileBuilder()
.addDomain("cat.1.test", "1B-1_TEST")
.addDomain("cat.1.test", "1B-1TEST")
.addRegistrar("1.test")
.addNameserver("ns1.cat.1.test", "17-ROID")
.addNameserver("ns2.cat.2.test", "19-ROID")
@@ -1298,7 +1298,7 @@ class RdapDomainSearchActionTest extends RdapSearchActionTestCase<RdapDomainSear
RequestType.NS_LDH_NAME,
"ns*.cat.1.test",
jsonFileBuilder()
.addDomain("cat.1.test", "1B-1_TEST")
.addDomain("cat.1.test", "1B-1TEST")
.addRegistrar("1.test")
.addNameserver("ns1.cat.1.test", "17-ROID")
.addNameserver("ns2.cat.2.test", "19-ROID")
@@ -74,7 +74,9 @@ class RdapNameserverActionTest extends RdapActionBaseTestCase<RdapNameserverActi
.that(generateActualJson("invalid/host/name"))
.isEqualTo(
generateExpectedJsonError(
"invalid/host/name is not a valid nameserver: Invalid host name", 400));
"invalid/host/name is not a valid nameserver: Host names can only contain a-z, 0-9,"
+ " '.', '_', and '-'",
400));
assertThat(response.getStatus()).isEqualTo(400);
}
@@ -420,8 +420,7 @@ public final class DatabaseHelper {
public static Tld createTld(String tld, ImmutableSortedMap<DateTime, TldState> tldStates) {
// Coerce the TLD string into a valid ROID suffix.
String roidSuffix =
Ascii.toUpperCase(tld.replaceFirst(ACE_PREFIX_REGEX, "").replace('.', '_'))
.replace('-', '_');
Ascii.toUpperCase(tld.replaceFirst(ACE_PREFIX_REGEX, "").replace(".", "")).replace("-", "");
return createTld(
tld, roidSuffix.length() > 8 ? roidSuffix.substring(0, 8) : roidSuffix, tldStates);
}
@@ -0,0 +1,161 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tmch;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.RegistryEnvironment.PRODUCTION;
import static google.registry.util.RegistryEnvironment.SANDBOX;
import static org.joda.time.DateTime.now;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Splitter;
import google.registry.model.smd.SignedMarkRevocationList;
import google.registry.model.smd.SignedMarkRevocationListDao;
import google.registry.model.tmch.ClaimsListDao;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.testing.FakeClock;
import google.registry.util.RegistryEnvironment;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
public class RstTmchUtilsIntTest {
private final FakeClock clock = new FakeClock();
@RegisterExtension
final JpaTestExtensions.JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
private static final String TMCH_CLAIM_LABEL = "tmch";
// RST label found in *.rst.dnl.csv resources. Currently both files are identical
private static final String RST_CLAIM_LABEL = "test--validate";
private static final String TMCH_SMD_ID = "tmch";
// RST label found in *.rst.smdrl.csv resources. Currently both files are identical
private static final String RST_SMD_ID = "0000001761385117375880-65535";
private static final String TMCH_DNL =
"""
1,2024-09-13T02:21:12.0Z
DNL,lookup-key,insertion-datetime
LABEL,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z
"""
.replace("LABEL", TMCH_CLAIM_LABEL);
private static final String TMCH_SMDRL =
"""
1,2022-11-22T01:49:36.9Z
smd-id,insertion-datetime
ID,2013-07-15T00:00:00.0Z
"""
.replace("ID", TMCH_SMD_ID);
@BeforeEach
void setup() throws Exception {
Splitter lineSplitter = Splitter.on("\n").omitEmptyStrings().trimResults();
tm().transact(
() -> ClaimsListDao.save(ClaimsListParser.parse(lineSplitter.splitToList(TMCH_DNL))));
tm().transact(
() ->
SignedMarkRevocationListDao.save(
SmdrlCsvParser.parse(lineSplitter.splitToList(TMCH_SMDRL))));
}
@ParameterizedTest
@MethodSource("provideTestCases")
@SuppressWarnings("unused") // testCaseName
void getClaimsList_production(String testCaseName, String tld) {
var currEnv = RegistryEnvironment.get();
try {
PRODUCTION.setup();
var claimsList = ClaimsListDao.get(tld);
assertThat(claimsList.getClaimKey(TMCH_CLAIM_LABEL)).isPresent();
assertThat(claimsList.getClaimKey(RST_CLAIM_LABEL)).isEmpty();
} finally {
currEnv.setup();
}
}
@ParameterizedTest
@MethodSource("provideTestCases")
@SuppressWarnings("unused") // testCaseName
void getSmdrList_production(String testCaseName, String tld) {
var currEnv = RegistryEnvironment.get();
try {
PRODUCTION.setup();
var smdrl = SignedMarkRevocationList.get(tld);
assertThat(smdrl.isSmdRevoked(TMCH_SMD_ID, now(UTC))).isTrue();
assertThat(smdrl.isSmdRevoked(RST_SMD_ID, now(UTC))).isFalse();
assertThat(smdrl.size()).isEqualTo(1);
} finally {
currEnv.setup();
}
}
@ParameterizedTest
@MethodSource("provideTestCases")
@SuppressWarnings("unused") // testCaseName
void getClaimsList_sandbox(String testCaseName, String tld) {
var currEnv = RegistryEnvironment.get();
try {
SANDBOX.setup();
var claimsList = ClaimsListDao.get(tld);
if (tld.equals("app")) {
assertThat(claimsList.getClaimKey(TMCH_CLAIM_LABEL)).isPresent();
assertThat(claimsList.getClaimKey(RST_CLAIM_LABEL)).isEmpty();
} else {
assertThat(claimsList.getClaimKey(TMCH_CLAIM_LABEL)).isEmpty();
// Currently ote and prod have the same data.
assertThat(claimsList.getClaimKey(RST_CLAIM_LABEL)).isPresent();
}
} finally {
currEnv.setup();
}
}
@ParameterizedTest
@MethodSource("provideTestCases")
@SuppressWarnings("unused") // testCaseName
void getSmdrList_sandbox(String testCaseName, String tld) {
var currEnv = RegistryEnvironment.get();
try {
SANDBOX.setup();
var smdrList = SignedMarkRevocationList.get(tld);
if (tld.equals("app")) {
assertThat(smdrList.size()).isEqualTo(1);
assertThat(smdrList.isSmdRevoked(TMCH_SMD_ID, now(UTC))).isTrue();
assertThat(smdrList.isSmdRevoked(RST_SMD_ID, now(UTC))).isFalse();
} else {
// Currently ote and prod have the same data.
assertThat(smdrList.size()).isEqualTo(5);
assertThat(smdrList.isSmdRevoked(TMCH_SMD_ID, now())).isFalse();
assertThat(smdrList.isSmdRevoked(RST_SMD_ID, now())).isTrue();
}
} finally {
currEnv.setup();
}
}
private static Stream<Arguments> provideTestCases() {
return Stream.of(
Arguments.of("NotRST", "app"),
Arguments.of("OTE", "cc-rst-test-tld-1"),
Arguments.of("PROD", "zz--idn-123"));
}
}
@@ -0,0 +1,117 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tmch;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.tmch.RstTmchUtils.getClaimsList;
import static google.registry.tmch.RstTmchUtils.getSmdrList;
import static google.registry.util.RegistryEnvironment.PRODUCTION;
import static google.registry.util.RegistryEnvironment.SANDBOX;
import google.registry.util.RegistryEnvironment;
import java.util.stream.Stream;
import org.joda.time.DateTime;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
public class RstTmchUtilsTest {
@ParameterizedTest
@MethodSource("provideTestCases")
@SuppressWarnings("unused") // testCaseName
void getClaimsList_production(String testCaseName, String tld) {
var currEnv = RegistryEnvironment.get();
try {
PRODUCTION.setup();
assertThat(getClaimsList(tld)).isEmpty();
} finally {
currEnv.setup();
}
}
@ParameterizedTest
@MethodSource("provideTestCases")
@SuppressWarnings("unused") // testCaseName
void getSmdrList_production(String testCaseName, String tld) {
var currEnv = RegistryEnvironment.get();
try {
PRODUCTION.setup();
assertThat(getSmdrList(tld)).isEmpty();
} finally {
currEnv.setup();
}
}
@ParameterizedTest
@MethodSource("provideTestCases")
@SuppressWarnings("unused") // testCaseName
void getClaimsList_sandbox(String testCaseName, String tld) {
var currEnv = RegistryEnvironment.get();
try {
SANDBOX.setup();
var claimsListOptional = getClaimsList(tld);
if (tld.equals("app")) {
assertThat(claimsListOptional).isEmpty();
} else {
// Currently ote and prod have the same data.
var claimsList = claimsListOptional.get();
assertThat(claimsList.getClaimKey("test-and-validate")).isPresent();
var labelsToKeys = claimsList.getLabelsToKeys();
assertThat(labelsToKeys).hasSize(8);
assertThat(labelsToKeys)
.containsEntry(
"test---validate", "2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001");
}
} finally {
currEnv.setup();
}
}
@ParameterizedTest
@MethodSource("provideTestCases")
@SuppressWarnings("unused") // testCaseName
void getSmdrList_sandbox(String testCaseName, String tld) {
var currEnv = RegistryEnvironment.get();
try {
SANDBOX.setup();
var smdrListOptional = getSmdrList(tld);
if (tld.equals("app")) {
assertThat(smdrListOptional).isEmpty();
} else {
// Currently ote and prod have the same data.
var smdrList = smdrListOptional.get();
assertThat(smdrList.size()).isEqualTo(5);
assertThat(
smdrList.isSmdRevoked(
"000000541526299609231-65535", DateTime.parse("2018-05-14T17:52:23.6Z")))
.isFalse();
assertThat(
smdrList.isSmdRevoked(
"000000541526299609231-65535", DateTime.parse("2018-05-14T17:52:23.7Z")))
.isTrue();
}
} finally {
currEnv.setup();
}
}
private static Stream<Arguments> provideTestCases() {
return Stream.of(
Arguments.of("NotRST", "app"),
Arguments.of("OTE", "cc-rst-test-tld-1"),
Arguments.of("PROD", "zz--idn-123"));
}
}
@@ -57,7 +57,7 @@ class TmchTestDataExpirationTest {
String tmchData = loadFile(TmchTestDataExpirationTest.class, filePath);
EncodedSignedMark smd = TmchData.readEncodedSignedMark(tmchData);
try {
tmchUtils.verifyEncodedSignedMark(smd, DateTime.now(UTC));
tmchUtils.verifyEncodedSignedMark("", smd, DateTime.now(UTC));
} catch (EppException e) {
throw new AssertionError("Error verifying signed mark " + filePath, e);
}
@@ -368,7 +368,7 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
"TLDSTR", name, "TLDUNICODE", name, "ROIDSUFFIX", "TLLLLLLLLLLLLLLLLLLLLLLD")));
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> runCommandForced("--input=" + tldFile));
assertThat(thrown.getMessage()).isEqualTo("ROID suffix must be in format ^[A-Z\\d_]{1,8}$");
assertThat(thrown.getMessage()).isEqualTo("ROID suffix must be in format ^[A-Z\\d]{1,8}$");
}
@Test
@@ -26,6 +26,11 @@
<rdeIDN:urlPolicy>https://www.registry.google/about/policies/domainabuse/</rdeIDN:urlPolicy>
</rdeIDN:idnTableRef>
<rdeIDN:idnTableRef id="augmented_latin">
<rdeIDN:url>https://www.iana.org/domains/idn-tables/tables/google_latn_3.0.txt</rdeIDN:url>
<rdeIDN:urlPolicy>https://www.registry.google/about/policies/domainabuse/</rdeIDN:urlPolicy>
</rdeIDN:idnTableRef>
<rdeIDN:idnTableRef id="ja">
<rdeIDN:url>https://www.iana.org/domains/idn-tables/tables/google_ja_1.0.txt</rdeIDN:url>
<rdeIDN:urlPolicy>https://www.registry.google/about/policies/domainabuse/</rdeIDN:urlPolicy>
@@ -37,7 +42,7 @@
<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>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeIDN-1.0">3</rdeHeader:count>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeIDN-1.0">4</rdeHeader:count>
</rdeHeader:header>
</rde:contents>
@@ -14,6 +14,6 @@
<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>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeIDN-1.0">3</rdeHeader:count>
<rdeHeader:count uri="urn:ietf:params:xml:ns:rdeIDN-1.0">4</rdeHeader:count>
</rdeHeader:header>
</rdeReport:report>
@@ -0,0 +1,54 @@
<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="0">example1.tld</domain:name>
<domain:reason>In use</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="1">example2.tld</domain:name>
</domain:cd>
<domain:cd>
<domain:name avail="1">example3.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:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example2.tld</fee:objID>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example3.tld</fee:objID>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -0,0 +1,17 @@
<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:check>
</check>
<extension>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:fee-0.12">
<fee:command name="Create">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -0,0 +1,58 @@
<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="1">example1.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:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">11.70</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>example1.tld</fee:objID>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:command name="update">
<fee:period unit="y">1</fee:period>
<fee:fee description="update">0.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -0,0 +1,24 @@
<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: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:command name="create" />
<fee:command name="renew" />
<fee:command name="transfer" />
<fee:command name="restore" />
<fee:command name="update" />
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -0,0 +1,58 @@
<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="1">example1.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: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="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:command name="update">
<fee:period unit="y">1</fee:period>
<fee:fee description="update">0.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -0,0 +1,58 @@
<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="1">example1.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:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">13.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>example1.tld</fee:objID>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>example1.tld</fee:objID>
<fee:command name="update">
<fee:period unit="y">1</fee:period>
<fee:fee description="update">0.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -0,0 +1,19 @@
<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:check>
</check>
<extension>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:command name="create" />
<fee:command name="renew" />
<fee:command name="transfer" />
<fee:command name="restore" />
<fee:command name="update" />
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -0,0 +1,45 @@
<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">example.example</domain:name>
</domain:cd>
<domain:cd>
<domain:name avail="true">example.tld</domain:name>
</domain:cd>
</domain:chkData>
</resData>
<extension>
<fee:chkData xmlns:fee="urn:ietf:params:xml:ns:fee-0.12"
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<fee:cd>
<fee:object>
<domain:name>example.example</domain:name>
</fee:object>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">800</fee:fee>
<fee:fee description="Early Access Period, fee expires: 294247-01-10T04:00:54.775Z">800</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:object>
<domain:name>example.tld</domain:name>
</fee:object>
<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>
@@ -0,0 +1,18 @@
<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>example.tld</domain:name>
<domain:name>example.example</domain:name>
</domain:check>
</check>
<extension>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:command name="create">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -0,0 +1,16 @@
<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>example.example</domain:name>
<domain:name>example.tld</domain:name>
</domain:check>
</check>
<extension>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:fee-0.12">
<fee:command name="create" />
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -0,0 +1,61 @@
<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="1">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>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>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:cd>
<fee:objID>rich.example</fee:objID>
<fee:class>premium</fee:class>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">100.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>rich.example</fee:objID>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>rich.example</fee:objID>
<fee:command name="update">
<fee:period unit="y">1</fee:period>
<fee:fee description="update">0.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -0,0 +1,31 @@
<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">rich.example</domain:name>
<domain:reason>In use</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>rich.example</fee:objID>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">%RENEWPRICE%</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -0,0 +1,64 @@
<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">rich.example</domain:name>
<domain:reason>In use</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>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>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:cd>
<fee:objID>rich.example</fee:objID>
<fee:class>premium</fee:class>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">100.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>rich.example</fee:objID>
<fee:class>premium</fee:class>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">100.00</fee:fee>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>rich.example</fee:objID>
<fee:command name="update">
<fee:period unit="y">1</fee:period>
<fee:fee description="update">0.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -1,4 +1,4 @@
domain_check_fee_premium_response_v12.xml<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
@@ -1,4 +1,4 @@
domain_check_fee_premium_response_v12.xml<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
@@ -1,4 +1,4 @@
domain_check_fee_premium_response_v12.xml<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
@@ -0,0 +1,19 @@
<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>rich.example</domain:name>
</domain:check>
</check>
<extension>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:command name="create" />
<fee:command name="renew" />
<fee:command name="transfer" />
<fee:command name="restore" />
<fee:command name="update" />
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -0,0 +1,15 @@
<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>rich.example</domain:name>
</domain:check>
</check>
<extension>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:command name="renew" />
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -0,0 +1,122 @@
<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="0">reserved.tld</domain:name>
<domain:reason>Reserved</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">allowedinsunrise.tld</domain:name>
<domain:reason>In use</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">allowedinsunrise.tld</domain:name>
<domain:reason>In use</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">premiumcollision.tld</domain:name>
<domain:reason>Cannot be delegated</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>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>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>reserved.tld</fee:objID>
<fee:command name="transfer">
<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="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>allowedinsunrise.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>allowedinsunrise.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>allowedinsunrise.tld</fee:objID>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>allowedinsunrise.tld</fee:objID>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>premiumcollision.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>premiumcollision.tld</fee:objID>
<fee:class>premium</fee:class>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">70.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>premiumcollision.tld</fee:objID>
<fee:class>premium</fee:class>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">70.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>premiumcollision.tld</fee:objID>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -0,0 +1,33 @@
<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>reserved.tld</domain:name>
<domain:name>allowedinsunrise.tld</domain:name>
<domain:name>allowedinsunrise.tld</domain:name>
<domain:name>premiumcollision.tld</domain:name>
</domain:check>
</check>
<extension>
<launch:check xmlns:launch="urn:ietf:params:xml:ns:launch-1.0" type="avail">
<launch:phase name="foo">custom</launch:phase>
</launch:check>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:currency>USD</fee:currency>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
</fee:command>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
</fee:command>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
</fee:command>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -0,0 +1,150 @@
<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="0">reserved.tld</domain:name>
<domain:reason>Reserved</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">allowedinsunrise.tld</domain:name>
<domain:reason>Reserved</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">collision.tld</domain:name>
<domain:reason>Cannot be delegated</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="0">premiumcollision.tld</domain:name>
<domain:reason>Cannot be delegated</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>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>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>reserved.tld</fee:objID>
<fee:command name="transfer">
<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="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>allowedinsunrise.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>allowedinsunrise.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>allowedinsunrise.tld</fee:objID>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>allowedinsunrise.tld</fee:objID>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>collision.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>collision.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>collision.tld</fee:objID>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>collision.tld</fee:objID>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>premiumcollision.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>premiumcollision.tld</fee:objID>
<fee:class>premium</fee:class>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">70.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>premiumcollision.tld</fee:objID>
<fee:class>premium</fee:class>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">70.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>premiumcollision.tld</fee:objID>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -64,8 +64,8 @@
<fee:currency>USD</fee:currency>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">70.00</fee:fee>
<fee:class>premium</fee:class>
<fee:fee description="restore">17.00</fee:fee>
<fee:class>premium</fee:class>
</fee:cd>
</fee:chkData>
</extension>
@@ -0,0 +1,33 @@
<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>reserved.tld</domain:name>
<domain:name>allowedinsunrise.tld</domain:name>
<domain:name>collision.tld</domain:name>
<domain:name>premiumcollision.tld</domain:name>
</domain:check>
</check>
<extension>
<launch:check xmlns:launch="urn:ietf:params:xml:ns:launch-1.0" type="avail">
<launch:phase name="foo">custom</launch:phase>
</launch:check>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:epp:fee-1.0">
<fee:currency>USD</fee:currency>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
</fee:command>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
</fee:command>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
</fee:command>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
</fee:command>
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -0,0 +1,153 @@
<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="0">reserved.tld</domain:name>
<domain:reason>Reserved</domain:reason>
</domain:cd>
<domain:cd>
<domain:name avail="1">allowedinsunrise.tld</domain:name>
</domain:cd>
<domain:cd>
<domain:name avail="1">collision.tld</domain:name>
</domain:cd>
<domain:cd>
<domain:name avail="1">premiumcollision.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>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>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>reserved.tld</fee:objID>
<fee:command name="transfer">
<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="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>allowedinsunrise.tld</fee:objID>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">11.05</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>allowedinsunrise.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>allowedinsunrise.tld</fee:objID>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>allowedinsunrise.tld</fee:objID>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>collision.tld</fee:objID>
<fee:class>collision</fee:class>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">11.05</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>collision.tld</fee:objID>
<fee:class>collision</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>collision.tld</fee:objID>
<fee:class>collision</fee:class>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">11.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>collision.tld</fee:objID>
<fee:class>collision</fee:class>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>premiumcollision.tld</fee:objID>
<fee:class>premium-collision</fee:class>
<fee:command name="create">
<fee:period unit="y">1</fee:period>
<fee:fee description="create">59.50</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>premiumcollision.tld</fee:objID>
<fee:class>premium-collision</fee:class>
<fee:command name="renew">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">70.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>premiumcollision.tld</fee:objID>
<fee:class>premium-collision</fee:class>
<fee:command name="transfer">
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">70.00</fee:fee>
</fee:command>
</fee:cd>
<fee:cd>
<fee:objID>premiumcollision.tld</fee:objID>
<fee:class>collision</fee:class>
<fee:command name="restore">
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</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