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

Compare commits

...

8 Commits

Author SHA1 Message Date
Lai Jiang ab4cecba22 Temporarily disable spec 11 pipeline deployment in GCB (#755)
The current setup causes the GCB job to fail validation and not run because it
uses backticks in the configuration yaml, which is not allowed -- there is no
shell to perform backtick substitution. See the error message here:

https://spinnaker.endpoints.domain-registry-dev.cloud.goog/gate/pipelines/01EF5GRMD625613H6Z033DBD3Z

In the future please make sure to test the GCB pipeline as instructed in
the comments at the beginning of each file before committing.

I tried to work around it by downloading the nomulus tool jar file
instead (running the nomulus-tool docker image inside a docker image is
not advisable). However the "nomulus deploy_spec11_pipeline" command
still fails. I'm not sure why. Has the command itself been tested
locally? The error message is shown below:

```
Step #2: Aug 09, 2020 3:11:46 AM org.apache.beam.runners.dataflow.DataflowRunner fromOptions
Step #2: WARNING: --region not set; will default to us-central1. Future releases of Beam will require the user to set the region explicitly. https://cloud.google.com/compute/docs/regions-zones/regions-zones
Step #2: Aug 09, 2020 3:11:46 AM org.apache.beam.sdk.extensions.gcp.options.GcpOptions$GcpTempLocationFactory tryCreateDefaultBucket
Step #2: INFO: No tempLocation specified, attempting to use default bucket: dataflow-staging-us-central1-937378958468
Step #2: Aug 09, 2020 3:11:47 AM org.apache.beam.sdk.extensions.gcp.util.RetryHttpRequestInitializer$LoggingHttpBackOffHandler handleResponse
Step #2: WARNING: Request failed with code 409, performed 0 retries due to IOExceptions, performed 0 retries due to unsuccessful status codes, HTTP framework says request can be retried, (caller responsible for retrying): https://www.googleapis.com/storage/v1/b?predefinedAcl=projectPrivate&predefinedDefaultObjectAcl=projectPrivate&project=domain-registry-alpha.
Step #2: Exception in thread "main"
Step #2: java.lang.RuntimeException: Failed to construct instance from factory method DataflowRunner#fromOptions(interface org.apache.beam.sdk.options.PipelineOptions)
Step #2:        at org.apache.beam.sdk.util.InstanceBuilder.buildFromMethod(InstanceBuilder.java:224)
Step #2:        at org.apache.beam.sdk.util.InstanceBuilder.build(InstanceBuilder.java:155)
Step #2:
Step #2:        at org.apache.beam.sdk.PipelineRunner.fromOptions(PipelineRunner.java:55)
Step #2:        at org.apache.beam.sdk.Pipeline.create(Pipeline.java:147)
Step #2:
Step #2:        at google.registry.beam.spec11.Spec11Pipeline.deploy(Spec11Pipeline.java:157)
Step #2:        at google.registry.tools.DeploySpec11PipelineCommand.run(DeploySpec11PipelineCommand.java:80)
Step #2:        at google.registry.tools.RegistryCli.runCommand(RegistryCli.java:257)
Step #2:        at google.registry.tools.RegistryCli.run(RegistryCli.java:182)
Step #2:        at google.registry.tools.RegistryTool.main(RegistryTool.java:129)
Step #2: Caused by: java.lang.reflect.InvocationTargetException
Step #2:        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Step #2:
Step #2:        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
Step #2:        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
Step #2:        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
Step #2:        at org.apache.beam.sdk.util.InstanceBuilder.buildFromMethod(InstanceBuilder.java:214)
Step #2:        ... 8 more
Step #2: Caused by: java.lang.IllegalArgumentException: Unable to use ClassLoader to detect classpath elements. Current ClassLoader is jdk.internal.loader.ClassLoaders$AppClassLoader@5cb0d902, only URLClassLoaders are supported.
Step #2:        at org.apache.beam.runners.core.construction.PipelineResources.detectClassPathResourcesToStage(PipelineResources.java:58)
Step #2:
Step #2:        at org.apache.beam.runners.dataflow.DataflowRunner.fromOptions(DataflowRunner.java:285)
Step #2:
Step #2:        ... 13 more
```

Lastly the "--project" flag refers to the KMS project. While I'm not
sure which project is that, I don't think we can use the PROJECT_ID
variable as this is a GCB-substituted variable which refers to the
project that the GCB job runs in, which in our cases means
domain-registry-dev. We shouldn't use that project for KMS. I've changed
it to the same project as the one we are deploying to, but please note
that we have a separate project ${project_id}-keys that is used for all
KMS purposes. This is specified in the config file so if that's what you
meant to use, there is no need to specify it in the command line. Actually
if you meant use the project to be deployed to for KMS, it also
shouldn't be necessary to specify it separately as this information is
already known when you specified "nomulus -e ENV".

https://team.git.corp.google.com/domain-registry-eng/nomulus-internal/+/refs/heads/master/core/src/main/java/google/registry/config/files/nomulus-config-production.yaml#168

Can you add more description on what the KMS project is supposed to be?
I don't think we specify a project for KMS purpose in any other
commands.

Given that there are several unresolved issues, I've commented out my
proposed solution so that deployment can proceed.
2020-08-09 22:41:31 -04:00
Lai Jiang 00941ddc3d Disable tests in RC builds (#752)
For reasons unclear at the moment the tests are not passing. Disabling
them for now so that release candidates can be built. We have CI runs
after each merge so we should be pretty confident if the build is broken
or not.
2020-08-07 17:51:34 -04:00
Shicong Huang 7b2b49dd53 Fix vkey construction in PollMessage and BillingEvent (#747)
* Fix vkey construction in PollMessage and BillingEvent

* Remove null check
2020-08-07 15:38:53 -04:00
Lai Jiang bc8df8f34e Exclude a test that fails in GCB build using Java 11 (#748) 2020-08-07 10:53:46 -04:00
Ben McIlwain 74b4424509 Improve and consolidate testing of poll messages/billing events (#745)
* Improve and consolidate testing of poll messages/billing events
2020-08-06 21:13:27 -04:00
Shicong Huang 486bf32353 Add SQL schema for GracePeriod (#709)
* Add SQL schema for GracePeriod

* Remove the join table

* Add a domainRepoId in GracePeriod

* Move the clone logic to GracePeriod

* Rebase on HEAD
2020-08-06 10:26:19 -04:00
Ben McIlwain b2a78b5d68 Allow allocation token discounts on premiums and for multiple years (#744)
* Allow allocation token discounts on premiums and for multiple years

* Add domain check flow tests

* Address code review comments

* Update schema file
2020-08-05 17:54:47 -04:00
Lai Jiang 95f4ae0e3a Use nodesource to install node (#742)
The node installed by nvm gives errors when running "npm install".

Also installs Python as it is need. Presumbly the system provided npm
version has python as a dependency so it was installed when npm was
installed.
2020-08-05 14:56:40 -04:00
59 changed files with 1211 additions and 430 deletions
+2
View File
@@ -63,6 +63,8 @@ def dockerIncompatibleTestPatterns = [
// methods, so we exclude the whole test class.
"google/registry/tools/params/PathParameterTest.*",
"google/registry/persistence/PersistenceModuleTest.*",
// This test is failing in docker when using Java 11. The cause is unclear.
"google/registry/tools/DomainLockUtilsTest.*",
]
// Tests that conflict with members of both the main test suite and the
@@ -360,7 +360,8 @@ public class DomainCreateFlow implements TransactionalFlow {
command.getNameservers().stream().collect(toImmutableSet()))
.setStatusValues(statuses.build())
.setContacts(command.getContacts())
.addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.ADD, createBillingEvent))
.addGracePeriod(
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, repoId, createBillingEvent))
.build();
entitiesToSave.add(
newDomain,
@@ -186,14 +186,18 @@ public final class DomainDeleteFlow implements TransactionalFlow {
DateTime redemptionTime = now.plus(redemptionGracePeriodLength);
asyncTaskEnqueuer.enqueueAsyncResave(
existingDomain, now, ImmutableSortedSet.of(redemptionTime, deletionTime));
builder.setDeletionTime(deletionTime)
builder
.setDeletionTime(deletionTime)
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
// Clear out all old grace periods and add REDEMPTION, which does not include a key to a
// billing event because there isn't one for a domain delete.
.setGracePeriods(ImmutableSet.of(GracePeriod.createWithoutBillingEvent(
GracePeriodStatus.REDEMPTION,
redemptionTime,
clientId)));
.setGracePeriods(
ImmutableSet.of(
GracePeriod.createWithoutBillingEvent(
GracePeriodStatus.REDEMPTION,
existingDomain.getRepoId(),
redemptionTime,
clientId)));
// Note: The expiration time is unchanged, so if it's before the new deletion time, there will
// be a "phantom autorenew" where the expiration time advances. No poll message will be
// produced (since we are ending the autorenew recurrences at "now" below) and the billing
@@ -60,7 +60,7 @@ public final class DomainPricingLogic {
* <p>If {@code allocationToken} is present and the domain is non-premium, that discount will be
* applied to the first year.
*/
public FeesAndCredits getCreatePrice(
FeesAndCredits getCreatePrice(
Registry registry,
String domainName,
DateTime dateTime,
@@ -104,8 +104,8 @@ public final class DomainPricingLogic {
/** Returns a new renew price for the pricer. */
@SuppressWarnings("unused")
public FeesAndCredits getRenewPrice(
Registry registry, String domainName, DateTime dateTime, int years) throws EppException {
FeesAndCredits getRenewPrice(Registry registry, String domainName, DateTime dateTime, int years)
throws EppException {
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
BigDecimal renewCost = domainPrices.getRenewCost().multipliedBy(years).getAmount();
return customLogic.customizeRenewPrice(
@@ -123,7 +123,7 @@ public final class DomainPricingLogic {
}
/** Returns a new restore price for the pricer. */
public FeesAndCredits getRestorePrice(
FeesAndCredits getRestorePrice(
Registry registry, String domainName, DateTime dateTime, boolean isExpired)
throws EppException {
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
@@ -147,7 +147,7 @@ public final class DomainPricingLogic {
}
/** Returns a new transfer price for the pricer. */
public FeesAndCredits getTransferPrice(Registry registry, String domainName, DateTime dateTime)
FeesAndCredits getTransferPrice(Registry registry, String domainName, DateTime dateTime)
throws EppException {
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
return customLogic.customizeTransferPrice(
@@ -168,7 +168,7 @@ public final class DomainPricingLogic {
}
/** Returns a new update price for the pricer. */
public FeesAndCredits getUpdatePrice(Registry registry, String domainName, DateTime dateTime)
FeesAndCredits getUpdatePrice(Registry registry, String domainName, DateTime dateTime)
throws EppException {
CurrencyUnit currency = registry.getCurrency();
BaseFee feeOrCredit = Fee.create(zeroInCurrency(currency), FeeType.UPDATE, false);
@@ -191,16 +191,20 @@ public final class DomainPricingLogic {
throws EppException {
if (allocationToken.isPresent()
&& allocationToken.get().getDiscountFraction() != 0.0
&& domainPrices.isPremium()) {
&& domainPrices.isPremium()
&& !allocationToken.get().shouldDiscountPremiums()) {
throw new AllocationTokenInvalidForPremiumNameException();
}
Money oneYearCreateCost = domainPrices.getCreateCost();
Money totalDomainCreateCost = oneYearCreateCost.multipliedBy(years);
// If a discount is applicable, apply it only to the first year
// Apply the allocation token discount, if applicable.
if (allocationToken.isPresent()) {
int discountedYears = Math.min(years, allocationToken.get().getDiscountYears());
Money discount =
oneYearCreateCost.multipliedBy(
allocationToken.get().getDiscountFraction(), RoundingMode.HALF_UP);
discountedYears * allocationToken.get().getDiscountFraction(),
RoundingMode.HALF_EVEN);
totalDomainCreateCost = totalDomainCreateCost.minus(discount);
}
return totalDomainCreateCost;
@@ -209,7 +213,7 @@ public final class DomainPricingLogic {
/** An allocation token was provided that is invalid for premium domains. */
public static class AllocationTokenInvalidForPremiumNameException
extends CommandUseErrorException {
public AllocationTokenInvalidForPremiumNameException() {
AllocationTokenInvalidForPremiumNameException() {
super("A nonzero discount code cannot be applied to premium domains");
}
}
@@ -183,7 +183,8 @@ public final class DomainRenewFlow implements TransactionalFlow {
.setAutorenewBillingEvent(newAutorenewEvent.createVKey())
.setAutorenewPollMessage(newAutorenewPollMessage.createVKey())
.addGracePeriod(
GracePeriod.forBillingEvent(GracePeriodStatus.RENEW, explicitRenewEvent))
GracePeriod.forBillingEvent(
GracePeriodStatus.RENEW, existingDomain.getRepoId(), explicitRenewEvent))
.build();
EntityChanges entityChanges =
flowCustomLogic.beforeSave(
@@ -192,7 +192,10 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.setGracePeriods(
billingEvent.isPresent()
? ImmutableSet.of(
GracePeriod.forBillingEvent(GracePeriodStatus.TRANSFER, billingEvent.get()))
GracePeriod.forBillingEvent(
GracePeriodStatus.TRANSFER,
existingDomain.getRepoId(),
billingEvent.get()))
: ImmutableSet.of())
.setLastEppUpdateTime(now)
.setLastEppUpdateClientId(clientId)
@@ -14,7 +14,6 @@
package google.registry.model;
import com.google.common.annotations.VisibleForTesting;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.MappedSuperclass;
@@ -30,6 +29,7 @@ import javax.xml.bind.annotation.XmlTransient;
*/
@MappedSuperclass
public abstract class BackupGroupRoot extends ImmutableObject {
/**
* An automatically managed timestamp of when this object was last written to Datastore.
*
@@ -40,7 +40,6 @@ public abstract class BackupGroupRoot extends ImmutableObject {
// Prevents subclasses from unexpectedly accessing as property (e.g., HostResource), which would
// require an unnecessary non-private setter method.
@Access(AccessType.FIELD)
@VisibleForTesting
UpdateAutoTimestamp updateTimestamp = UpdateAutoTimestamp.create(null);
/** Get the {@link UpdateAutoTimestamp} for this entity. */
@@ -347,27 +347,11 @@ public abstract class BillingEvent extends ImmutableObject
@Override
public VKey<OneTime> createVKey() {
return VKey.create(
getClass(),
parent.getParent().getName() + "/" + parent.getId() + "/" + getId(),
Key.create(this));
return VKey.create(getClass(), getId(), Key.create(this));
}
public static VKey<OneTime> createVKey(Key<OneTime> key) {
// TODO(b/159207551): As it stands, the SQL key generated here doesn't mesh with the primary
// key type for the table, which is a single long integer.
if (key == null) {
return null;
}
Key parent = key.getParent();
Key grandparent = (parent != null) ? parent.getParent() : null;
String path =
(grandparent != null ? grandparent.getName() : "")
+ "/"
+ (parent != null ? parent.getId() : "")
+ "/"
+ key.getId();
return VKey.create(OneTime.class, path, key);
return VKey.create(OneTime.class, key.getId(), key);
}
@Override
@@ -505,21 +489,11 @@ public abstract class BillingEvent extends ImmutableObject
@Override
public VKey<Recurring> createVKey() {
return VKey.create(
getClass(),
parent.getParent().getName() + "/" + parent.getId() + "/" + getId(),
Key.create(this));
return VKey.create(getClass(), getId(), Key.create(this));
}
public static VKey<Recurring> createVKey(Key<Recurring> key) {
// TODO(b/159207551): As it stands, the SQL key generated here doesn't mesh with the primary
// key type for the table, which is a single long integer.
if (key == null) {
return null;
}
String path =
key.getParent().getParent().getName() + "/" + key.getParent().getId() + "/" + key.getId();
return VKey.create(Recurring.class, path, key);
return VKey.create(Recurring.class, key.getId(), key);
}
@Override
@@ -639,21 +613,11 @@ public abstract class BillingEvent extends ImmutableObject
@Override
public VKey<Cancellation> createVKey() {
return VKey.create(
getClass(),
parent.getParent().getName() + "/" + parent.getId() + "/" + getId(),
Key.create(this));
return VKey.create(getClass(), getId(), Key.create(this));
}
public static VKey<Cancellation> createVKey(Key<Cancellation> key) {
// TODO(b/159207551): As it stands, the SQL key generated here doesn't mesh with the primary
// key type for the table, which is a single long integer.
if (key == null) {
return null;
}
String path =
key.getParent().getParent().getName() + "/" + key.getParent().getId() + "/" + key.getId();
return VKey.create(Cancellation.class, path, key);
return VKey.create(Cancellation.class, key.getId(), key);
}
@Override
@@ -733,21 +697,11 @@ public abstract class BillingEvent extends ImmutableObject
@Override
public VKey<Modification> createVKey() {
return VKey.create(
getClass(),
parent.getParent().getName() + "/" + parent.getId() + "/" + getId(),
Key.create(this));
return VKey.create(getClass(), getId(), Key.create(this));
}
public static VKey<Modification> createVKey(Key<Modification> key) {
// TODO(b/159207551): As it stands, the SQL key generated here doesn't mesh with the primary
// key type for the table, which is a single long integer.
if (key == null) {
return null;
}
String path =
key.getParent().getParent().getName() + "/" + key.getParent().getId() + "/" + key.getId();
return VKey.create(Modification.class, path, key);
return VKey.create(Modification.class, key.getId(), key);
}
/**
@@ -14,6 +14,7 @@
package google.registry.model.domain;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.EppResource;
@@ -27,9 +28,13 @@ import google.registry.schema.replay.DatastoreAndSqlEntity;
import java.util.Set;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import org.joda.time.DateTime;
/**
@@ -74,6 +79,17 @@ public class DomainBase extends DomainContent
return super.nsHosts;
}
@Access(AccessType.PROPERTY)
@OneToMany(
cascade = {CascadeType.ALL},
fetch = FetchType.EAGER,
orphanRemoval = true)
@JoinColumn(name = "domainRepoId", referencedColumnName = "repoId")
@SuppressWarnings("UnusedMethod")
private Set<GracePeriod> getInternalGracePeriods() {
return gracePeriods;
}
@Override
public VKey<DomainBase> createVKey() {
return VKey.create(DomainBase.class, getRepoId(), Key.create(this));
@@ -73,7 +73,6 @@ import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.MappedSuperclass;
@@ -234,7 +233,7 @@ public class DomainContent extends EppResource
VKey<PollMessage.Autorenew> autorenewPollMessage;
/** The unexpired grace periods for this domain (some of which may not be active yet). */
@Transient @ElementCollection Set<GracePeriod> gracePeriods;
@Transient Set<GracePeriod> gracePeriods;
/**
* The id of the signed mark that was used to create this domain in sunrise.
@@ -260,6 +259,18 @@ public class DomainContent extends EppResource
allContacts =
allContacts.stream().map(contact -> contact.reconstitute()).collect(toImmutableSet());
setContactFields(allContacts, true);
// We have to return the cloned object here because the original object's
// hashcode is not correct due to the change to its domainRepoId. The cloned
// object will have a null hashcode so that it can get a recalculated hashcode
// when its hashCode() is invoked.
// TODO(b/162739503): Remove this after fully migrating to Cloud SQL.
if (gracePeriods != null) {
gracePeriods =
gracePeriods.stream()
.map(gracePeriod -> gracePeriod.cloneWithDomainRepoId(getRepoId()))
.collect(toImmutableSet());
}
}
@PostLoad
@@ -354,6 +365,12 @@ public class DomainContent extends EppResource
this.nsHosts = nsHosts;
}
// Hibernate needs this in order to populate gracePeriods but no one else should ever use it
@SuppressWarnings("UnusedMethod")
private void setInternalGracePeriods(Set<GracePeriod> gracePeriods) {
this.gracePeriods = gracePeriods;
}
public final String getCurrentSponsorClientId() {
return getPersistedCurrentSponsorClientId();
}
@@ -448,6 +465,7 @@ public class DomainContent extends EppResource
ImmutableSet.of(
GracePeriod.create(
GracePeriodStatus.TRANSFER,
domain.getRepoId(),
transferExpirationTime.plus(
Registry.get(domain.getTld()).getTransferGracePeriodLength()),
transferData.getGainingClientId(),
@@ -483,6 +501,7 @@ public class DomainContent extends EppResource
.addGracePeriod(
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
lastAutorenewTime.plus(
Registry.get(domain.getTld()).getAutoRenewGracePeriodLength()),
domain.getCurrentSponsorClientId(),
@@ -534,8 +553,10 @@ public class DomainContent extends EppResource
/** Loads and returns the fully qualified host names of all linked nameservers. */
public ImmutableSortedSet<String> loadNameserverHostNames() {
return ofy().load()
.keys(getNameservers().stream().map(VKey::getOfyKey).collect(toImmutableSet())).values()
return ofy()
.load()
.keys(getNameservers().stream().map(VKey::getOfyKey).collect(toImmutableSet()))
.values()
.stream()
.map(HostResource::getHostName)
.collect(toImmutableSortedSet(Ordering.natural()));
@@ -38,10 +38,10 @@ import javax.persistence.JoinTable;
@Entity
@javax.persistence.Table(
indexes = {
@javax.persistence.Index(columnList = "creationTime"),
@javax.persistence.Index(columnList = "historyRegistrarId"),
@javax.persistence.Index(columnList = "historyType"),
@javax.persistence.Index(columnList = "historyModificationTime")
@javax.persistence.Index(columnList = "creationTime"),
@javax.persistence.Index(columnList = "historyRegistrarId"),
@javax.persistence.Index(columnList = "historyType"),
@javax.persistence.Index(columnList = "historyModificationTime")
})
public class DomainHistory extends HistoryEntry {
// Store DomainContent instead of DomainBase so we don't pick up its @Id
@@ -18,16 +18,14 @@ import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.persistence.VKey;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.Table;
import org.joda.time.DateTime;
/**
@@ -37,75 +35,13 @@ import org.joda.time.DateTime;
* the resource is loaded from Datastore.
*/
@Embed
@javax.persistence.Entity
public class GracePeriod extends ImmutableObject {
@javax.persistence.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Ignore
/** Unique id required for hibernate representation. */
long id;
/** The type of grace period. */
GracePeriodStatus type;
/** When the grace period ends. */
DateTime expirationTime;
/** The registrar to bill. */
@Column(name = "registrarId")
String clientId;
/**
* The one-time billing event corresponding to the action that triggered this grace period, or
* null if not applicable. Not set for autorenew grace periods (which instead use the field {@code
* billingEventRecurring}) or for redemption grace periods (since deletes have no cost).
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
VKey<BillingEvent.OneTime> billingEventOneTime = null;
/**
* The recurring billing event corresponding to the action that triggered this grace period, if
* applicable - i.e. if the action was an autorenew - or null in all other cases.
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
VKey<BillingEvent.Recurring> billingEventRecurring = null;
public GracePeriodStatus getType() {
return type;
}
public DateTime getExpirationTime() {
return expirationTime;
}
public String getClientId() {
return clientId;
}
/** Returns true if this GracePeriod has an associated BillingEvent; i.e. if it's refundable. */
public boolean hasBillingEvent() {
return billingEventOneTime != null || billingEventRecurring != null;
}
/**
* Returns the one time billing event. The value will only be non-null if the type of this grace
* period is not AUTO_RENEW.
*/
public VKey<BillingEvent.OneTime> getOneTimeBillingEvent() {
return billingEventOneTime;
}
/**
* Returns the recurring billing event. The value will only be non-null if the type of this grace
* period is AUTO_RENEW.
*/
public VKey<BillingEvent.Recurring> getRecurringBillingEvent() {
return billingEventRecurring;
}
@Entity
@Table(indexes = @Index(columnList = "domainRepoId"))
public class GracePeriod extends GracePeriodBase {
private static GracePeriod createInternal(
GracePeriodStatus type,
String domainRepoId,
DateTime expirationTime,
String clientId,
@Nullable VKey<BillingEvent.OneTime> billingEventOneTime,
@@ -117,6 +53,7 @@ public class GracePeriod extends ImmutableObject {
"Recurring billing events must be present on (and only on) autorenew grace periods");
GracePeriod instance = new GracePeriod();
instance.type = checkArgumentNotNull(type);
instance.domainRepoId = checkArgumentNotNull(domainRepoId);
instance.expirationTime = checkArgumentNotNull(expirationTime);
instance.clientId = checkArgumentNotNull(clientId);
instance.billingEventOneTime = billingEventOneTime;
@@ -133,32 +70,51 @@ public class GracePeriod extends ImmutableObject {
*/
public static GracePeriod create(
GracePeriodStatus type,
String domainRepoId,
DateTime expirationTime,
String clientId,
@Nullable VKey<BillingEvent.OneTime> billingEventOneTime) {
return createInternal(type, expirationTime, clientId, billingEventOneTime, null);
return createInternal(type, domainRepoId, expirationTime, clientId, billingEventOneTime, null);
}
/** Creates a GracePeriod for a Recurring billing event. */
public static GracePeriod createForRecurring(
GracePeriodStatus type,
String domainRepoId,
DateTime expirationTime,
String clientId,
VKey<Recurring> billingEventRecurring) {
checkArgumentNotNull(billingEventRecurring, "billingEventRecurring cannot be null");
return createInternal(type, expirationTime, clientId, null, billingEventRecurring);
return createInternal(
type, domainRepoId, expirationTime, clientId, null, billingEventRecurring);
}
/** Creates a GracePeriod with no billing event. */
public static GracePeriod createWithoutBillingEvent(
GracePeriodStatus type, DateTime expirationTime, String clientId) {
return createInternal(type, expirationTime, clientId, null, null);
GracePeriodStatus type, String domainRepoId, DateTime expirationTime, String clientId) {
return createInternal(type, domainRepoId, expirationTime, clientId, null, null);
}
/** Constructs a GracePeriod of the given type from the provided one-time BillingEvent. */
public static GracePeriod forBillingEvent(
GracePeriodStatus type, BillingEvent.OneTime billingEvent) {
GracePeriodStatus type, String domainRepoId, BillingEvent.OneTime billingEvent) {
return create(
type, billingEvent.getBillingTime(), billingEvent.getClientId(), billingEvent.createVKey());
type,
domainRepoId,
billingEvent.getBillingTime(),
billingEvent.getClientId(),
billingEvent.createVKey());
}
/**
* Returns a clone of this {@link GracePeriod} with {@link #domainRepoId} set to the given value.
*
* <p>TODO(b/162739503): Remove this function after fully migrating to Cloud SQL.
*/
public GracePeriod cloneWithDomainRepoId(String domainRepoId) {
GracePeriod clone = clone(this);
clone.domainRepoId = checkArgumentNotNull(domainRepoId);
return clone;
}
}
@@ -0,0 +1,114 @@
// Copyright 2020 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.model.domain;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.OneTime;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.persistence.VKey;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.MappedSuperclass;
import org.joda.time.DateTime;
/** Base class containing common fields and methods for {@link GracePeriod}. */
@Embed
@MappedSuperclass
public class GracePeriodBase extends ImmutableObject {
/** Unique id required for hibernate representation. */
@javax.persistence.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Ignore
Long id;
/** Repository id for the domain which this grace period belongs to. */
@Ignore
@Column(nullable = false)
String domainRepoId;
/** The type of grace period. */
@Column(nullable = false)
@Enumerated(EnumType.STRING)
GracePeriodStatus type;
/** When the grace period ends. */
@Column(nullable = false)
DateTime expirationTime;
/** The registrar to bill. */
@Column(name = "registrarId", nullable = false)
String clientId;
/**
* The one-time billing event corresponding to the action that triggered this grace period, or
* null if not applicable. Not set for autorenew grace periods (which instead use the field {@code
* billingEventRecurring}) or for redemption grace periods (since deletes have no cost).
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
@Column(name = "billing_event_id")
VKey<OneTime> billingEventOneTime = null;
/**
* The recurring billing event corresponding to the action that triggered this grace period, if
* applicable - i.e. if the action was an autorenew - or null in all other cases.
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
@Column(name = "billing_recurrence_id")
VKey<BillingEvent.Recurring> billingEventRecurring = null;
public GracePeriodStatus getType() {
return type;
}
public String getDomainRepoId() {
return domainRepoId;
}
public DateTime getExpirationTime() {
return expirationTime;
}
public String getClientId() {
return clientId;
}
/** Returns true if this GracePeriod has an associated BillingEvent; i.e. if it's refundable. */
public boolean hasBillingEvent() {
return billingEventOneTime != null || billingEventRecurring != null;
}
/**
* Returns the one time billing event. The value will only be non-null if the type of this grace
* period is not AUTO_RENEW.
*/
public VKey<BillingEvent.OneTime> getOneTimeBillingEvent() {
return billingEventOneTime;
}
/**
* Returns the recurring billing event. The value will only be non-null if the type of this grace
* period is AUTO_RENEW.
*/
public VKey<BillingEvent.Recurring> getRecurringBillingEvent() {
return billingEventRecurring;
}
}
@@ -29,12 +29,14 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Range;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.Mapify;
import com.googlecode.objectify.annotation.OnLoad;
import google.registry.flows.EppException;
import google.registry.flows.domain.DomainFlowUtils;
import google.registry.model.BackupGroupRoot;
@@ -108,9 +110,22 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
*/
double discountFraction;
/** Whether the discount fraction (if any) also applies to premium names. Defaults to false. */
boolean discountPremiums;
/** Up to how many years of initial creation receive the discount (if any). Defaults to 1. */
int discountYears = 1;
/** The type of the token, either single-use or unlimited-use. */
// TODO(b/130301183): this should not be nullable, we can remove this once we're sure it isn't
@Nullable TokenType tokenType;
TokenType tokenType;
// TODO: Remove onLoad once all allocation tokens are migrated to have a discountYears of 1.
@OnLoad
void onLoad() {
if (discountYears == 0) {
discountYears = 1;
}
}
/**
* Promotional token validity periods.
@@ -146,8 +161,8 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
return token;
}
public Key<HistoryEntry> getRedemptionHistoryEntry() {
return redemptionHistoryEntry;
public Optional<Key<HistoryEntry>> getRedemptionHistoryEntry() {
return Optional.ofNullable(redemptionHistoryEntry);
}
public boolean isRedeemed() {
@@ -174,6 +189,16 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
return discountFraction;
}
public boolean shouldDiscountPremiums() {
return discountPremiums;
}
public int getDiscountYears() {
// Allocation tokens created prior to the addition of the discountYears field will have a value
// of 0 for it, but it should be the default value of 1 to retain the previous behavior.
return Math.max(1, discountYears);
}
public TokenType getTokenType() {
return tokenType;
}
@@ -193,6 +218,7 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
/** A builder for constructing {@link AllocationToken} objects, since they are immutable. */
public static class Builder extends Buildable.Builder<AllocationToken> {
public Builder() {}
private Builder(AllocationToken instance) {
@@ -210,6 +236,12 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
getInstance().redemptionHistoryEntry == null
|| TokenType.SINGLE_USE.equals(getInstance().tokenType),
"Redemption history entry can only be specified for SINGLE_USE tokens");
checkArgument(
getInstance().discountFraction > 0 || !getInstance().discountPremiums,
"Discount premiums can only be specified along with a discount fraction");
checkArgument(
getInstance().discountFraction > 0 || getInstance().discountYears == 1,
"Discount years can only be specified along with a discount fraction");
if (getInstance().domainName != null) {
try {
DomainFlowUtils.validateDomainName(getInstance().domainName);
@@ -258,10 +290,26 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
}
public Builder setDiscountFraction(double discountFraction) {
checkArgument(
Range.closed(0.0d, 1.0d).contains(discountFraction),
"Discount fraction must be between 0 and 1 inclusive");
getInstance().discountFraction = discountFraction;
return this;
}
public Builder setDiscountPremiums(boolean discountPremiums) {
getInstance().discountPremiums = discountPremiums;
return this;
}
public Builder setDiscountYears(int discountYears) {
checkArgument(
Range.closed(1, 10).contains(discountYears),
"Discount years must be between 1 and 10 inclusive");
getInstance().discountYears = discountYears;
return this;
}
public Builder setTokenType(TokenType tokenType) {
checkState(getInstance().tokenType == null, "Token type can only be set once");
getInstance().tokenType = tokenType;
@@ -155,15 +155,7 @@ public abstract class PollMessage extends ImmutableObject
public abstract VKey<? extends PollMessage> createVKey();
public static VKey<PollMessage> createVKey(Key<PollMessage> key) {
// TODO(b/159207551): As it stands, the SQL key generated here doesn't mesh with the primary key
// type for the table, which is a single long integer. Also note that the class id is not
// correct here and as such the resulting key will not be loadable from SQL.
if (key == null) {
return null;
}
String path =
key.getParent().getParent().getName() + "/" + key.getParent().getId() + key.getId();
return VKey.create(PollMessage.class, path, key);
return VKey.create(PollMessage.class, key.getId(), key);
}
/** Override Buildable.asBuilder() to give this method stronger typing. */
@@ -115,7 +115,20 @@ class GenerateAllocationTokensCommand implements CommandWithRemoteApi {
description =
"A discount off the base price for the first year between 0.0 and 1.0. Default is 0.0,"
+ " i.e. no discount.")
private double discountFraction;
private Double discountFraction;
@Parameter(
names = {"--discount_premiums"},
description =
"Whether the discount is valid for premium names in addition to standard ones. Default"
+ " is false.",
arity = 1)
private Boolean discountPremiums;
@Parameter(
names = {"--discount_years"},
description = "The number of years the discount applies for. Default is 1, max value is 10.")
private Integer discountYears;
@Parameter(
names = "--token_status_transitions",
@@ -170,8 +183,10 @@ class GenerateAllocationTokensCommand implements CommandWithRemoteApi {
.setToken(t)
.setTokenType(tokenType == null ? SINGLE_USE : tokenType)
.setAllowedClientIds(ImmutableSet.copyOf(nullToEmpty(allowedClientIds)))
.setAllowedTlds(ImmutableSet.copyOf(nullToEmpty(allowedTlds)))
.setDiscountFraction(discountFraction);
.setAllowedTlds(ImmutableSet.copyOf(nullToEmpty(allowedTlds)));
Optional.ofNullable(discountFraction).ifPresent(token::setDiscountFraction);
Optional.ofNullable(discountPremiums).ifPresent(token::setDiscountPremiums);
Optional.ofNullable(discountYears).ifPresent(token::setDiscountYears);
Optional.ofNullable(tokenStatusTransitions)
.ifPresent(token::setTokenStatusTransitions);
Optional.ofNullable(domainNames)
@@ -27,7 +27,7 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.domain.token.AllocationToken;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/** Command to show allocation tokens. */
@Parameters(separators = " =", commandDescription = "Show allocation token(s)")
@@ -55,11 +55,11 @@ final class GetAllocationTokenCommand implements CommandWithRemoteApi {
if (loadedTokens.containsKey(token)) {
AllocationToken loadedToken = loadedTokens.get(token);
System.out.println(loadedToken.toString());
if (loadedToken.getRedemptionHistoryEntry() == null) {
if (!loadedToken.getRedemptionHistoryEntry().isPresent()) {
System.out.printf("Token %s was not redeemed.\n", token);
} else {
DomainBase domain =
domains.get(loadedToken.getRedemptionHistoryEntry().<DomainBase>getParent());
domains.get(loadedToken.getRedemptionHistoryEntry().get().<DomainBase>getParent());
if (domain == null) {
System.out.printf("ERROR: Token %s was redeemed but domain can't be loaded.\n", token);
} else {
@@ -80,7 +80,8 @@ final class GetAllocationTokenCommand implements CommandWithRemoteApi {
ImmutableList<Key<DomainBase>> domainKeys =
tokens.stream()
.map(AllocationToken::getRedemptionHistoryEntry)
.filter(Objects::nonNull)
.filter(Optional::isPresent)
.map(Optional::get)
.map(Key::<DomainBase>getParent)
.collect(toImmutableList());
ImmutableMap.Builder<Key<DomainBase>, DomainBase> domainsBuilder = new ImmutableMap.Builder<>();
@@ -66,6 +66,19 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
+ "i.e. no discount.")
private Double discountFraction;
@Parameter(
names = {"--discount_premiums"},
description =
"Whether the discount is valid for premium names in addition to standard ones. Default"
+ " is false.",
arity = 1)
private Boolean discountPremiums;
@Parameter(
names = {"--discount_years"},
description = "The number of years the discount applies for. Default is 1, max value is 10.")
private Integer discountYears;
@Parameter(
names = "--token_status_transitions",
converter = TokenStatusTransitions.class,
@@ -122,6 +135,8 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
Optional.ofNullable(allowedTlds)
.ifPresent(tlds -> builder.setAllowedTlds(ImmutableSet.copyOf(tlds)));
Optional.ofNullable(discountFraction).ifPresent(builder::setDiscountFraction);
Optional.ofNullable(discountPremiums).ifPresent(builder::setDiscountPremiums);
Optional.ofNullable(discountYears).ifPresent(builder::setDiscountYears);
Optional.ofNullable(tokenStatusTransitions).ifPresent(builder::setTokenStatusTransitions);
return builder.build();
}
@@ -117,9 +117,10 @@ public class ResaveEntityActionTest {
@Test
void test_domainPendingDeletion_isResavedAndReenqueued() {
DomainBase newDomain = newDomainBase("domain.tld");
DomainBase domain =
persistResource(
newDomainBase("domain.tld")
newDomain
.asBuilder()
.setDeletionTime(clock.nowUtc().plusDays(35))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
@@ -127,6 +128,7 @@ public class ResaveEntityActionTest {
ImmutableSet.of(
GracePeriod.createWithoutBillingEvent(
GracePeriodStatus.REDEMPTION,
newDomain.getRepoId(),
clock.nowUtc().plusDays(30),
"TheRegistrar")))
.build());
@@ -161,6 +161,7 @@ public class DomainBaseUtilTest {
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.ADD,
"4-COM",
fakeClock.nowUtc().plusDays(1),
"registrar",
null))
@@ -214,7 +214,11 @@ class InitSqlPipelineTest {
.setSmdId("smdid")
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.ADD, fakeClock.nowUtc().plusDays(1), "registrar", null))
GracePeriodStatus.ADD,
"4-COM",
fakeClock.nowUtc().plusDays(1),
"registrar",
null))
.build());
exportDir = store.export(exportRootDir.getAbsolutePath(), ALL_KINDS, ImmutableSet.of());
commitLogDir = Files.createDirectory(tmpDir.resolve("commits")).toFile();
@@ -16,15 +16,12 @@ package google.registry.flows;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Sets.difference;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.model.eppcommon.EppXmlTransformer.marshal;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatastoreHelper.POLL_MESSAGE_ID_STRIPPER;
import static google.registry.testing.DatastoreHelper.getPollMessages;
import static google.registry.testing.DatastoreHelper.stripBillingEventId;
import static google.registry.xml.XmlTestUtils.assertXmlEquals;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -34,7 +31,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.ObjectArrays;
import com.google.common.collect.Streams;
import google.registry.config.RegistryConfig.ConfigModule.TmchCaMode;
import google.registry.flows.EppTestComponent.FakesAndMocksModule;
import google.registry.flows.picker.FlowPicker;
@@ -44,7 +40,6 @@ import google.registry.model.eppcommon.ProtocolDefinition;
import google.registry.model.eppinput.EppInput;
import google.registry.model.eppoutput.EppOutput;
import google.registry.model.ofy.Ofy;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tmch.ClaimsListShard.ClaimsListSingleton;
import google.registry.monitoring.whitebox.EppMetric;
@@ -185,6 +180,7 @@ public abstract class FlowTestCase<F extends Flow> {
builder.put(
GracePeriod.create(
entry.getKey().getType(),
entry.getKey().getDomainRepoId(),
entry.getKey().getExpirationTime(),
entry.getKey().getClientId(),
null),
@@ -213,24 +209,6 @@ public abstract class FlowTestCase<F extends Flow> {
.isEqualTo(canonicalizeGracePeriods(expected));
}
protected void assertPollMessages(String clientId, PollMessage... expected) {
assertPollMessagesHelper(getPollMessages(clientId), expected);
}
protected void assertPollMessages(PollMessage... expected) {
assertPollMessagesHelper(getPollMessages(), expected);
}
/** Assert that the list matches all the poll messages in the fake Datastore. */
private void assertPollMessagesHelper(
Iterable<PollMessage> pollMessages, PollMessage... expected) {
// Ordering is irrelevant but duplicates should be considered independently.
assertThat(
Streams.stream(pollMessages).map(POLL_MESSAGE_ID_STRIPPER).collect(toImmutableList()))
.containsExactlyElementsIn(
Arrays.stream(expected).map(POLL_MESSAGE_ID_STRIPPER).collect(toImmutableList()));
}
private EppOutput runFlowInternal(CommitMode commitMode, UserPrivileges userPrivileges)
throws Exception {
eppMetricBuilder = EppMetric.builderForRequest(clock);
@@ -282,13 +282,14 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
}
@Test
void testSuccess_allocationTokenPromotion() throws Exception {
void testSuccess_allocationTokenPromotion_singleYear() throws Exception {
createTld("example");
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(UNLIMITED_USE)
.setDiscountFraction(0.5)
.setDiscountYears(2)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
@@ -300,6 +301,69 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_allocationtoken_fee_response.xml"));
}
@Test
void testSuccess_allocationTokenPromotion_multiYearAndPremiums() throws Exception {
createTld("example");
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDomainName("rich.example")
.setDiscountFraction(0.9)
.setDiscountYears(3)
.setDiscountPremiums(true)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
.put(clock.nowUtc().minusDays(1), TokenStatus.VALID)
.put(clock.nowUtc().plusDays(1), TokenStatus.ENDED)
.build())
.build());
setEppInput(
"domain_check_allocationtoken_promotion.xml", ImmutableMap.of("DOMAIN", "rich.example"));
runFlowAssertResponse(
loadFile(
"domain_check_allocationtoken_promotion_response.xml",
new ImmutableMap.Builder<String, String>()
.put("DOMAIN", "rich.example")
.put("COST_1YR", "10.00")
.put("COST_2YR", "20.00")
.put("COST_5YR", "230.00")
.put("FEE_CLASS", "<fee:class>premium</fee:class>")
.build()));
}
@Test
void testSuccess_allocationTokenPromotion_multiYear() throws Exception {
createTld("tld");
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDomainName("single.tld")
.setDiscountFraction(0.444)
.setDiscountYears(2)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
.put(clock.nowUtc().minusDays(1), TokenStatus.VALID)
.put(clock.nowUtc().plusDays(1), TokenStatus.ENDED)
.build())
.build());
setEppInput(
"domain_check_allocationtoken_promotion.xml", ImmutableMap.of("DOMAIN", "single.tld"));
runFlowAssertResponse(
loadFile(
"domain_check_allocationtoken_promotion_response.xml",
new ImmutableMap.Builder<String, String>()
.put("DOMAIN", "single.tld")
.put("COST_1YR", "7.23")
.put("COST_2YR", "14.46")
.put("COST_5YR", "53.46")
.put("FEE_CLASS", "")
.build()));
}
@Test
void testSuccess_promotionNotActive() throws Exception {
createTld("example");
@@ -68,6 +68,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig;
import google.registry.flows.EppException;
@@ -150,12 +151,12 @@ import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldState;
import google.registry.model.registry.Registry.TldType;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
@@ -324,7 +325,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertGracePeriods(
domain.getGracePeriods(),
ImmutableMap.of(
GracePeriod.create(GracePeriodStatus.ADD, billingTime, "TheRegistrar", null),
GracePeriod.create(
GracePeriodStatus.ADD, domain.getRepoId(), billingTime, "TheRegistrar", null),
createBillingEvent));
assertDnsTasksEnqueued(getUniqueIdFromCommand());
assertEppResourceIndexEntityFor(domain);
@@ -435,7 +437,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
@Test
void testFailure_invalidAllocationToken() {
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld"));
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistContactsAndHosts();
EppException thrown = assertThrows(InvalidAllocationTokenException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
@@ -445,7 +449,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
void testFailure_reservedDomainCreate_allocationTokenIsForADifferentDomain() {
// Try to register a reserved domain name with an allocation token valid for a different domain
// name.
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "resdom.tld"));
setEppInput(
"domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "resdom.tld", "YEARS", "2"));
persistContactsAndHosts();
persistResource(
new AllocationToken.Builder()
@@ -464,7 +469,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
void testFailure_nonreservedDomainCreate_allocationTokenIsForADifferentDomain() {
// Try to register a non-reserved domain name with an allocation token valid for a different
// domain name.
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld"));
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistContactsAndHosts();
persistResource(
new AllocationToken.Builder()
@@ -481,7 +488,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
@Test
void testFailure_alreadyRedemeedAllocationToken() {
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld"));
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistContactsAndHosts();
persistResource(
new AllocationToken.Builder()
@@ -497,7 +506,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
@Test
void testSuccess_validAllocationToken_isRedeemed() throws Exception {
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld"));
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistContactsAndHosts();
AllocationToken token =
persistResource(
@@ -508,12 +519,14 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
HistoryEntry historyEntry =
ofy().load().type(HistoryEntry.class).ancestor(reloadResourceByForeignKey()).first().now();
assertThat(ofy().load().entity(token).now().getRedemptionHistoryEntry())
.isEqualTo(Key.create(historyEntry));
.hasValue(Key.create(historyEntry));
}
@Test
void testSuccess_validAllocationToken_multiUse() throws Exception {
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld"));
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistContactsAndHosts();
allocationToken =
persistResource(
@@ -531,7 +544,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(), allocationToken);
clock.advanceOneMilli();
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "otherexample.tld"));
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "otherexample.tld", "YEARS", "2"));
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "otherexample.tld")));
}
@@ -543,8 +558,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistContactsAndHosts("foo.tld");
assertTransactionalFlow(true);
String expectedResponseXml =
loadFile(
"domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.foo.tld"));
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.foo.tld"));
runFlowAssertResponse(CommitMode.LIVE, UserPrivileges.NORMAL, expectedResponseXml);
assertSuccessfulCreate("foo.tld", ImmutableSet.of());
assertNoLordn();
@@ -576,8 +590,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_registration_encoded_signed_mark.xml",
ImmutableMap.of("DOMAIN", "test-validate.tld", "PHASE", "open"));
persistContactsAndHosts();
EppException thrown =
assertThrows(SignedMarksOnlyDuringSunriseException.class, this::runFlow);
EppException thrown = assertThrows(SignedMarksOnlyDuringSunriseException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@@ -1184,13 +1197,14 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
Registry.get("tld")
.asBuilder()
.setTldStateTransitions(
ImmutableSortedMap.of(
START_OF_TIME, PREDELEGATION,
DateTime.parse("1999-01-01T00:00:00Z"), QUIET_PERIOD,
// The anchor tenant is created here, on 1999-04-03
DateTime.parse("1999-07-01T00:00:00Z"), START_DATE_SUNRISE,
DateTime.parse("2000-01-01T00:00:00Z"), GENERAL_AVAILABILITY))
new ImmutableSortedMap.Builder<DateTime, TldState>(Ordering.natural())
.put(START_OF_TIME, PREDELEGATION)
.put(DateTime.parse("1999-01-01T00:00:00Z"), QUIET_PERIOD)
.put(DateTime.parse("1999-07-01T00:00:00Z"), START_DATE_SUNRISE)
.put(DateTime.parse("2000-01-01T00:00:00Z"), GENERAL_AVAILABILITY)
.build())
.build());
// The anchor tenant is created during the quiet period, on 1999-04-03.
setEppInput("domain_create_anchor_allocationtoken.xml");
persistContactsAndHosts();
runFlowAssertResponse(loadFile("domain_create_anchor_response.xml"));
@@ -1210,7 +1224,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.build());
// Despite the domain being FULLY_BLOCKED, the non-superuser create succeeds the domain is also
// RESERVED_FOR_SPECIFIC_USE and the correct allocation token is passed.
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "resdom.tld"));
setEppInput(
"domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "resdom.tld", "YEARS", "2"));
persistContactsAndHosts();
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "resdom.tld")));
@@ -1233,7 +1248,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.setTokenType(SINGLE_USE)
.setDomainName("resdom.tld")
.build());
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "resdom.tld"));
setEppInput(
"domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "resdom.tld", "YEARS", "2"));
persistContactsAndHosts();
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "resdom.tld")));
@@ -1247,7 +1263,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
ofy().load().key(Key.create(AllocationToken.class, token)).now();
assertThat(reloadedToken.isRedeemed()).isTrue();
assertThat(reloadedToken.getRedemptionHistoryEntry())
.isEqualTo(Key.create(getHistoryEntries(reloadResourceByForeignKey()).get(0)));
.hasValue(Key.create(getHistoryEntries(reloadResourceByForeignKey()).get(0)));
}
private void assertAllocationTokenWasNotRedeemed(String token) {
@@ -1264,7 +1280,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.UNLIMITED_USE)
.setTokenType(UNLIMITED_USE)
.setDiscountFraction(0.5)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
@@ -1274,7 +1290,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.build())
.build());
clock.advanceOneMilli();
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld"));
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
BillingEvent.OneTime billingEvent =
@@ -1284,14 +1302,131 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
}
@Test
void testSuccess_promotionDoesNotApplyToPremiumPrice() {
// At the moment, discounts cannot apply to premium domains
void testSuccess_allocationToken_multiYearDiscount_maxesAtTokenDiscountYears() throws Exception {
// 2yrs @ $13 + 3yrs @ $13 * (1 - 0.73) = $36.53
runTest_allocationToken_multiYearDiscount(false, 0.73, 3, Money.of(USD, 36.53));
}
@Test
void testSuccess_allocationToken_multiYearDiscount_maxesAtNumRegistrationYears()
throws Exception {
// 5yrs @ $13 * (1 - 0.276) = $47.06
runTest_allocationToken_multiYearDiscount(false, 0.276, 10, Money.of(USD, 47.06));
}
void runTest_allocationToken_multiYearDiscount(
boolean discountPremiums, double discountFraction, int discountYears, Money expectedPrice)
throws Exception {
persistContactsAndHosts();
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDomainName("example.tld")
.setDiscountFraction(discountFraction)
.setDiscountYears(discountYears)
.setDiscountPremiums(discountPremiums)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
.put(clock.nowUtc().plusMillis(1), TokenStatus.VALID)
.put(clock.nowUtc().plusSeconds(1), TokenStatus.ENDED)
.build())
.build());
clock.advanceOneMilli();
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "5"));
runFlowAssertResponse(
loadFile(
"domain_create_response_wildcard.xml",
new ImmutableMap.Builder<String, String>()
.put("DOMAIN", "example.tld")
.put("CRDATE", "1999-04-03T22:00:00.0Z")
.put("EXDATE", "2004-04-03T22:00:00.0Z")
.build()));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(ofy().load().type(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("example.tld");
assertThat(billingEvent.getCost()).isEqualTo(expectedPrice);
}
@Test
void testSuccess_allocationToken_multiYearDiscount_worksForPremiums() throws Exception {
createTld("example");
persistContactsAndHosts();
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.UNLIMITED_USE)
.setTokenType(SINGLE_USE)
.setDomainName("rich.example")
.setDiscountFraction(0.98)
.setDiscountYears(2)
.setDiscountPremiums(true)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
.put(clock.nowUtc().plusMillis(1), TokenStatus.VALID)
.put(clock.nowUtc().plusSeconds(1), TokenStatus.ENDED)
.build())
.build());
clock.advanceOneMilli();
setEppInput(
"domain_create_premium_allocationtoken.xml",
ImmutableMap.of("YEARS", "3", "FEE", "104.00"));
runFlowAssertResponse(
loadFile(
"domain_create_response_premium.xml",
ImmutableMap.of("EXDATE", "2002-04-03T22:00:00.0Z", "FEE", "104.00")));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(ofy().load().type(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("rich.example");
// 1yr @ $100 + 2yrs @ $100 * (1 - 0.98) = $104
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 104.00));
}
@Test
void testSuccess_allocationToken_singleYearDiscount_worksForPremiums() throws Exception {
createTld("example");
persistContactsAndHosts();
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDomainName("rich.example")
.setDiscountFraction(0.95555)
.setDiscountPremiums(true)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
.put(clock.nowUtc().plusMillis(1), TokenStatus.VALID)
.put(clock.nowUtc().plusSeconds(1), TokenStatus.ENDED)
.build())
.build());
clock.advanceOneMilli();
setEppInput(
"domain_create_premium_allocationtoken.xml",
ImmutableMap.of("YEARS", "3", "FEE", "204.44"));
runFlowAssertResponse(
loadFile(
"domain_create_response_premium.xml",
ImmutableMap.of("EXDATE", "2002-04-03T22:00:00.0Z", "FEE", "204.44")));
BillingEvent.OneTime billingEvent =
Iterables.getOnlyElement(ofy().load().type(BillingEvent.OneTime.class));
assertThat(billingEvent.getTargetId()).isEqualTo("rich.example");
// 2yrs @ $100 + 1yr @ $100 * (1 - 0.95555) = $204.44
assertThat(billingEvent.getCost()).isEqualTo(Money.of(USD, 204.44));
}
@Test
void testSuccess_promotionDoesNotApplyToPremiumPrice() {
// Discounts only apply to premium domains if the token is explicitly configured to allow it.
createTld("example");
persistContactsAndHosts();
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(UNLIMITED_USE)
.setDiscountFraction(0.5)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
@@ -1301,7 +1436,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.build())
.build());
clock.advanceOneMilli();
setEppInput("domain_create_premium_allocationtoken.xml");
setEppInput(
"domain_create_premium_allocationtoken.xml",
ImmutableMap.of("YEARS", "2", "FEE", "193.50"));
assertAboutEppExceptions()
.that(assertThrows(AllocationTokenInvalidForPremiumNameException.class, this::runFlow))
.marshalsToXml();
@@ -1313,7 +1450,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.UNLIMITED_USE)
.setTokenType(UNLIMITED_USE)
.setDiscountFraction(0.5)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
@@ -1322,7 +1459,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.put(clock.nowUtc().plusDays(60), TokenStatus.ENDED)
.build())
.build());
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld"));
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
assertAboutEppExceptions()
.that(assertThrows(AllocationTokenNotInPromotionException.class, this::runFlow))
.marshalsToXml();
@@ -1334,7 +1473,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.UNLIMITED_USE)
.setTokenType(UNLIMITED_USE)
.setAllowedTlds(ImmutableSet.of("example"))
.setDiscountFraction(0.5)
.setTokenStatusTransitions(
@@ -1344,7 +1483,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.put(clock.nowUtc().plusDays(1), TokenStatus.ENDED)
.build())
.build());
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld"));
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
assertAboutEppExceptions()
.that(assertThrows(AllocationTokenNotValidForTldException.class, this::runFlow))
.marshalsToXml();
@@ -1356,7 +1497,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.UNLIMITED_USE)
.setTokenType(UNLIMITED_USE)
.setAllowedClientIds(ImmutableSet.of("someClientId"))
.setDiscountFraction(0.5)
.setTokenStatusTransitions(
@@ -1366,7 +1507,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.put(clock.nowUtc().plusDays(1), TokenStatus.ENDED)
.build())
.build());
setEppInput("domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "example.tld"));
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
assertAboutEppExceptions()
.that(assertThrows(AllocationTokenNotValidForRegistrarException.class, this::runFlow))
.marshalsToXml();
@@ -1562,7 +1705,11 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
// Modify the Registrar to block premium names.
persistResource(loadRegistrar("TheRegistrar").asBuilder().setBlockPremiumNames(true).build());
runFlowAssertResponse(
CommitMode.LIVE, SUPERUSER, loadFile("domain_create_response_premium.xml"));
CommitMode.LIVE,
SUPERUSER,
loadFile(
"domain_create_response_premium.xml",
ImmutableMap.of("EXDATE", "2001-04-03T22:00:00.0Z", "FEE", "200.00")));
assertSuccessfulCreate("example", ImmutableSet.of());
}
@@ -36,6 +36,7 @@ import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_DELETE;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatastoreHelper.assertBillingEvents;
import static google.registry.testing.DatastoreHelper.assertPollMessages;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatastoreHelper.getOnlyPollMessage;
@@ -195,6 +196,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
ImmutableSet.of(
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
A_MONTH_AGO.plusDays(45),
"TheRegistrar",
autorenewBillingEvent.createVKey())))
@@ -290,7 +292,8 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
void testDryRun() throws Exception {
setUpSuccessfulTest();
setUpGracePeriods(
GracePeriod.create(GracePeriodStatus.ADD, TIME_BEFORE_FLOW.plusDays(1), "foo", null));
GracePeriod.create(
GracePeriodStatus.ADD, domain.getRepoId(), TIME_BEFORE_FLOW.plusDays(1), "foo", null));
dryRunFlowAssertResponse(loadFile("generic_success_response.xml"));
}
@@ -314,7 +317,8 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
setUpSuccessfulTest();
BillingEvent.OneTime graceBillingEvent =
persistResource(createBillingEvent(Reason.CREATE, Money.of(USD, 123)));
setUpGracePeriods(GracePeriod.forBillingEvent(gracePeriodStatus, graceBillingEvent));
setUpGracePeriods(
GracePeriod.forBillingEvent(gracePeriodStatus, domain.getRepoId(), graceBillingEvent));
// We should see exactly one poll message, which is for the autorenew 1 month in the future.
assertPollMessages(createAutorenewPollMessage("TheRegistrar").build());
clock.advanceOneMilli();
@@ -388,9 +392,14 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
BillingEvent.OneTime renewBillingEvent =
persistResource(createBillingEvent(Reason.RENEW, Money.of(USD, 456)));
setUpGracePeriods(
GracePeriod.forBillingEvent(GracePeriodStatus.RENEW, renewBillingEvent),
GracePeriod.forBillingEvent(GracePeriodStatus.RENEW, domain.getRepoId(), renewBillingEvent),
// This grace period has no associated billing event, so it won't cause a cancellation.
GracePeriod.create(GracePeriodStatus.TRANSFER, TIME_BEFORE_FLOW.plusDays(1), "foo", null));
GracePeriod.create(
GracePeriodStatus.TRANSFER,
domain.getRepoId(),
TIME_BEFORE_FLOW.plusDays(1),
"foo",
null));
// We should see exactly one poll message, which is for the autorenew 1 month in the future.
assertPollMessages(createAutorenewPollMessage("TheRegistrar").build());
DateTime expectedExpirationTime = domain.getRegistrationExpirationTime().minusYears(2);
@@ -424,6 +433,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
.containsExactly(
GracePeriod.create(
GracePeriodStatus.REDEMPTION,
domain.getRepoId(),
clock.nowUtc().plus(Registry.get("tld").getRedemptionGracePeriodLength()),
"TheRegistrar",
null));
@@ -624,6 +634,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
.containsExactly(
GracePeriod.create(
GracePeriodStatus.REDEMPTION,
domain.getRepoId(),
clock.nowUtc().plus(Registry.get("tld").getRedemptionGracePeriodLength()),
"TheRegistrar",
null));
@@ -696,7 +707,8 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
BillingEvent.OneTime graceBillingEvent =
persistResource(createBillingEvent(Reason.CREATE, Money.of(USD, 123)));
// Use a grace period so that the delete is immediate, simplifying the assertions below.
setUpGracePeriods(GracePeriod.forBillingEvent(GracePeriodStatus.ADD, graceBillingEvent));
setUpGracePeriods(
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, domain.getRepoId(), graceBillingEvent));
// Add a nameserver.
HostResource host = persistResource(newHostResource("ns1.example.tld"));
persistResource(
@@ -1004,7 +1016,11 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
setUpSuccessfulTest();
setUpGracePeriods(
GracePeriod.create(
GracePeriodStatus.ADD, TIME_BEFORE_FLOW.plusDays(1), "TheRegistrar", null));
GracePeriodStatus.ADD,
domain.getRepoId(),
TIME_BEFORE_FLOW.plusDays(1),
"TheRegistrar",
null));
setUpGracePeriodDurations();
clock.advanceOneMilli();
earlierHistoryEntry =
@@ -1027,7 +1043,11 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
setUpSuccessfulTest();
setUpGracePeriods(
GracePeriod.create(
GracePeriodStatus.ADD, TIME_BEFORE_FLOW.plusDays(1), "TheRegistrar", null));
GracePeriodStatus.ADD,
domain.getRepoId(),
TIME_BEFORE_FLOW.plusDays(1),
"TheRegistrar",
null));
setUpGracePeriodDurations();
clock.advanceOneMilli();
earlierHistoryEntry =
@@ -1081,6 +1101,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
.containsExactly(
GracePeriod.create(
GracePeriodStatus.REDEMPTION,
domain.getRepoId(),
clock.nowUtc().plus(standardDays(15)),
"TheRegistrar",
null));
@@ -1127,6 +1148,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
.containsExactly(
GracePeriod.create(
GracePeriodStatus.REDEMPTION,
domain.getRepoId(),
clock.nowUtc().plus(standardDays(15)),
"TheRegistrar",
null));
@@ -308,7 +308,8 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
domain
.asBuilder()
.addGracePeriod(
GracePeriod.create(gracePeriodStatus, clock.nowUtc().plusDays(1), "foo", null))
GracePeriod.create(
gracePeriodStatus, domain.getRepoId(), clock.nowUtc().plusDays(1), "foo", null))
.setCreationClientId("NewRegistrar")
.setCreationTimeForTest(DateTime.parse("2003-11-26T22:00:00.0Z"))
.setRegistrationExpirationTime(DateTime.parse("2005-11-26T22:00:00.0Z"))
@@ -337,7 +338,11 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
.asBuilder()
.addGracePeriod(
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW, clock.nowUtc().plusDays(1), "foo", recurringVKey))
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
recurringVKey))
.build());
doSuccessfulTest("domain_info_response_autorenewperiod.xml", false);
}
@@ -352,7 +357,11 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
.asBuilder()
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.REDEMPTION, clock.nowUtc().plusDays(1), "foo", null))
GracePeriodStatus.REDEMPTION,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
null))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.build());
doSuccessfulTest("domain_info_response_redemptionperiod.xml", false);
@@ -367,7 +376,11 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
.asBuilder()
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.RENEW, clock.nowUtc().plusDays(1), "foo", null))
GracePeriodStatus.RENEW,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
null))
.build());
doSuccessfulTest("domain_info_response_renewperiod.xml", false);
}
@@ -381,10 +394,18 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
.asBuilder()
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.RENEW, clock.nowUtc().plusDays(1), "foo", null))
GracePeriodStatus.RENEW,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
null))
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.RENEW, clock.nowUtc().plusDays(2), "foo", null))
GracePeriodStatus.RENEW,
domain.getRepoId(),
clock.nowUtc().plusDays(2),
"foo",
null))
.build());
doSuccessfulTest("domain_info_response_renewperiod.xml", false);
}
@@ -398,7 +419,11 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
.asBuilder()
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.TRANSFER, clock.nowUtc().plusDays(1), "foo", null))
GracePeriodStatus.TRANSFER,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
null))
.build());
doSuccessfulTest("domain_info_response_transferperiod.xml", false);
}
@@ -421,10 +446,19 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
domain
.asBuilder()
.addGracePeriod(
GracePeriod.create(GracePeriodStatus.ADD, clock.nowUtc().plusDays(1), "foo", null))
GracePeriod.create(
GracePeriodStatus.ADD,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
null))
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.RENEW, clock.nowUtc().plusDays(2), "foo", null))
GracePeriodStatus.RENEW,
domain.getRepoId(),
clock.nowUtc().plusDays(2),
"foo",
null))
.build());
doSuccessfulTest("domain_info_response_stackedaddrenewperiod.xml", false);
}
@@ -437,7 +471,12 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
domain
.asBuilder()
.addGracePeriod(
GracePeriod.create(GracePeriodStatus.ADD, clock.nowUtc().plusDays(1), "foo", null))
GracePeriod.create(
GracePeriodStatus.ADD,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
null))
.setDsData(
ImmutableSet.of(
DelegationSignerData.create(
@@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.flows.domain.DomainTransferFlowTestCase.persistWithPendingTransfer;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatastoreHelper.assertBillingEvents;
import static google.registry.testing.DatastoreHelper.assertPollMessages;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatastoreHelper.loadRegistrar;
@@ -236,6 +237,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, DomainBa
ImmutableMap.of(
GracePeriod.create(
GracePeriodStatus.RENEW,
domain.getRepoId(),
clock.nowUtc().plus(Registry.get("tld").getRenewGracePeriodLength()),
renewalClientId,
null),
@@ -17,6 +17,7 @@ package google.registry.flows.domain;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatastoreHelper.assertBillingEvents;
import static google.registry.testing.DatastoreHelper.assertPollMessages;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatastoreHelper.getPollMessages;
@@ -112,7 +113,11 @@ class DomainRestoreRequestFlowTest
.setDeletionTime(clock.nowUtc().plusDays(35))
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.REDEMPTION, clock.nowUtc().plusDays(1), "foo", null))
GracePeriodStatus.REDEMPTION,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"foo",
null))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.setDeletePollMessage(
persistResource(
@@ -288,6 +288,7 @@ class DomainTransferApproveFlowTest
ImmutableMap.of(
GracePeriod.create(
GracePeriodStatus.TRANSFER,
domain.getRepoId(),
clock.nowUtc().plus(registry.getTransferGracePeriodLength()),
"NewRegistrar",
null),
@@ -649,6 +650,7 @@ class DomainTransferApproveFlowTest
.addGracePeriod(
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
autorenewTime.plus(Registry.get("tld").getAutoRenewGracePeriodLength()),
"TheRegistrar",
existingAutorenewEvent))
@@ -21,6 +21,7 @@ import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_CANCEL;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST;
import static google.registry.testing.DatastoreHelper.assertBillingEvents;
import static google.registry.testing.DatastoreHelper.assertPollMessages;
import static google.registry.testing.DatastoreHelper.createPollMessageForImplicitTransfer;
import static google.registry.testing.DatastoreHelper.deleteResource;
import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType;
@@ -332,6 +332,7 @@ class DomainTransferRequestFlowTest
ImmutableMap.of(
GracePeriod.create(
GracePeriodStatus.TRANSFER,
domain.getRepoId(),
implicitTransferTime.plus(registry.getTransferGracePeriodLength()),
"NewRegistrar",
null),
@@ -962,6 +963,7 @@ class DomainTransferRequestFlowTest
.addGracePeriod(
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
autorenewTime.plus(Registry.get("tld").getAutoRenewGracePeriodLength()),
"TheRegistrar",
existingAutorenewEvent))
@@ -1088,6 +1090,7 @@ class DomainTransferRequestFlowTest
.addGracePeriod(
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
autorenewTime.plus(Registry.get("tld").getAutoRenewGracePeriodLength()),
"TheRegistrar",
domain.getAutorenewBillingEvent()))
@@ -1117,6 +1120,7 @@ class DomainTransferRequestFlowTest
.addGracePeriod(
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
autorenewTime.plus(Registry.get("tld").getAutoRenewGracePeriodLength()),
"TheRegistrar",
existingAutorenewEvent))
@@ -385,10 +385,11 @@ public class BillingEventTest extends EntityTestCase {
@Test
void testSuccess_cancellation_forGracePeriod_withOneTime() {
BillingEvent.Cancellation newCancellation = BillingEvent.Cancellation.forGracePeriod(
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, oneTime),
historyEntry2,
"foo.tld");
BillingEvent.Cancellation newCancellation =
BillingEvent.Cancellation.forGracePeriod(
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, domain.getRepoId(), oneTime),
historyEntry2,
"foo.tld");
// Set ID to be the same to ignore for the purposes of comparison.
newCancellation = newCancellation.asBuilder().setId(cancellationOneTime.getId()).build();
assertThat(newCancellation).isEqualTo(cancellationOneTime);
@@ -400,6 +401,7 @@ public class BillingEventTest extends EntityTestCase {
BillingEvent.Cancellation.forGracePeriod(
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
now.plusYears(1).plusDays(45),
"a registrar",
recurring.createVKey()),
@@ -418,7 +420,10 @@ public class BillingEventTest extends EntityTestCase {
() ->
BillingEvent.Cancellation.forGracePeriod(
GracePeriod.createWithoutBillingEvent(
GracePeriodStatus.REDEMPTION, now.plusDays(1), "a registrar"),
GracePeriodStatus.REDEMPTION,
domain.getRepoId(),
now.plusDays(1),
"a registrar"),
historyEntry,
"foo.tld"));
assertThat(thrown).hasMessageThat().contains("grace period without billing event");
@@ -18,6 +18,7 @@ import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableO
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.SqlHelper.assertThrowForeignKeyViolation;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
@@ -25,6 +26,7 @@ import com.google.common.collect.ImmutableSet;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.StatusValue;
@@ -100,6 +102,8 @@ public class DomainBaseSqlTest {
.setLaunchNotice(
LaunchNotice.create("tcnid", "validatorId", START_OF_TIME, START_OF_TIME))
.setSmdId("smdid")
.addGracePeriod(
GracePeriod.create(GracePeriodStatus.ADD, "4-COM", END_OF_TIME, "registrar1", null))
.build();
host =
@@ -154,6 +154,7 @@ public class DomainBaseTest extends EntityTestCase {
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.ADD,
"4-COM",
fakeClock.nowUtc().plusDays(1),
"registrar",
null))
@@ -371,7 +372,11 @@ public class DomainBaseTest extends EntityTestCase {
// Okay for billing event to be null since the point of this grace period is just
// to check that the transfer will clear all existing grace periods.
GracePeriod.create(
GracePeriodStatus.ADD, fakeClock.nowUtc().plusDays(100), "foo", null))
GracePeriodStatus.ADD,
domain.getRepoId(),
fakeClock.nowUtc().plusDays(100),
"foo",
null))
.build();
DomainBase afterTransfer = domain.cloneProjectedAtTime(fakeClock.nowUtc().plusDays(1));
DateTime newExpirationTime = oldExpirationTime.plusYears(1);
@@ -382,6 +387,7 @@ public class DomainBaseTest extends EntityTestCase {
.containsExactly(
GracePeriod.create(
GracePeriodStatus.TRANSFER,
domain.getRepoId(),
fakeClock
.nowUtc()
.plusDays(1)
@@ -499,9 +505,24 @@ public class DomainBaseTest extends EntityTestCase {
void testStackedGracePeriods() {
ImmutableList<GracePeriod> gracePeriods =
ImmutableList.of(
GracePeriod.create(GracePeriodStatus.ADD, fakeClock.nowUtc().plusDays(3), "foo", null),
GracePeriod.create(GracePeriodStatus.ADD, fakeClock.nowUtc().plusDays(2), "bar", null),
GracePeriod.create(GracePeriodStatus.ADD, fakeClock.nowUtc().plusDays(1), "baz", null));
GracePeriod.create(
GracePeriodStatus.ADD,
domain.getRepoId(),
fakeClock.nowUtc().plusDays(3),
"foo",
null),
GracePeriod.create(
GracePeriodStatus.ADD,
domain.getRepoId(),
fakeClock.nowUtc().plusDays(2),
"bar",
null),
GracePeriod.create(
GracePeriodStatus.ADD,
domain.getRepoId(),
fakeClock.nowUtc().plusDays(1),
"baz",
null));
domain = domain.asBuilder().setGracePeriods(ImmutableSet.copyOf(gracePeriods)).build();
for (int i = 1; i < 3; ++i) {
assertThat(domain.cloneProjectedAtTime(fakeClock.nowUtc().plusDays(i)).getGracePeriods())
@@ -513,14 +534,32 @@ public class DomainBaseTest extends EntityTestCase {
void testGracePeriodsByType() {
ImmutableSet<GracePeriod> addGracePeriods =
ImmutableSet.of(
GracePeriod.create(GracePeriodStatus.ADD, fakeClock.nowUtc().plusDays(3), "foo", null),
GracePeriod.create(GracePeriodStatus.ADD, fakeClock.nowUtc().plusDays(1), "baz", null));
GracePeriod.create(
GracePeriodStatus.ADD,
domain.getRepoId(),
fakeClock.nowUtc().plusDays(3),
"foo",
null),
GracePeriod.create(
GracePeriodStatus.ADD,
domain.getRepoId(),
fakeClock.nowUtc().plusDays(1),
"baz",
null));
ImmutableSet<GracePeriod> renewGracePeriods =
ImmutableSet.of(
GracePeriod.create(
GracePeriodStatus.RENEW, fakeClock.nowUtc().plusDays(3), "foo", null),
GracePeriodStatus.RENEW,
domain.getRepoId(),
fakeClock.nowUtc().plusDays(3),
"foo",
null),
GracePeriod.create(
GracePeriodStatus.RENEW, fakeClock.nowUtc().plusDays(1), "baz", null));
GracePeriodStatus.RENEW,
domain.getRepoId(),
fakeClock.nowUtc().plusDays(1),
"baz",
null));
domain =
domain
.asBuilder()
@@ -589,6 +628,7 @@ public class DomainBaseTest extends EntityTestCase {
.containsExactly(
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
oldExpirationTime
.plusYears(2)
.plus(Registry.get("com").getAutoRenewGracePeriodLength()),
@@ -757,6 +797,7 @@ public class DomainBaseTest extends EntityTestCase {
ImmutableSet.of(
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
now.plusDays(1),
"NewRegistrar",
recurringBillKey)))
@@ -63,8 +63,9 @@ public class GracePeriodTest {
@Test
void testSuccess_forBillingEvent() {
GracePeriod gracePeriod = GracePeriod.forBillingEvent(GracePeriodStatus.ADD, onetime);
GracePeriod gracePeriod = GracePeriod.forBillingEvent(GracePeriodStatus.ADD, "1-TEST", onetime);
assertThat(gracePeriod.getType()).isEqualTo(GracePeriodStatus.ADD);
assertThat(gracePeriod.getDomainRepoId()).isEqualTo("1-TEST");
assertThat(gracePeriod.getOneTimeBillingEvent()).isEqualTo(onetime.createVKey());
assertThat(gracePeriod.getRecurringBillingEvent()).isNull();
assertThat(gracePeriod.getClientId()).isEqualTo("TheRegistrar");
@@ -74,9 +75,11 @@ public class GracePeriodTest {
@Test
void testSuccess_createWithoutBillingEvent() {
GracePeriod gracePeriod = GracePeriod.createWithoutBillingEvent(
GracePeriodStatus.REDEMPTION, now, "TheRegistrar");
GracePeriod gracePeriod =
GracePeriod.createWithoutBillingEvent(
GracePeriodStatus.REDEMPTION, "1-TEST", now, "TheRegistrar");
assertThat(gracePeriod.getType()).isEqualTo(GracePeriodStatus.REDEMPTION);
assertThat(gracePeriod.getDomainRepoId()).isEqualTo("1-TEST");
assertThat(gracePeriod.getOneTimeBillingEvent()).isNull();
assertThat(gracePeriod.getRecurringBillingEvent()).isNull();
assertThat(gracePeriod.getClientId()).isEqualTo("TheRegistrar");
@@ -89,7 +92,7 @@ public class GracePeriodTest {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> GracePeriod.forBillingEvent(GracePeriodStatus.AUTO_RENEW, onetime));
() -> GracePeriod.forBillingEvent(GracePeriodStatus.AUTO_RENEW, "1-TEST", onetime));
assertThat(thrown).hasMessageThat().contains("autorenew");
}
@@ -101,6 +104,7 @@ public class GracePeriodTest {
() ->
GracePeriod.createForRecurring(
GracePeriodStatus.RENEW,
"1-TEST",
now.plusDays(1),
"TheRegistrar",
VKey.create(Recurring.class, 12345)));
@@ -44,7 +44,7 @@ import org.junit.jupiter.api.Test;
class AllocationTokenTest extends EntityTestCase {
@BeforeEach
void setup() {
void beforeEach() {
createTld("foo");
}
@@ -59,6 +59,8 @@ class AllocationTokenTest extends EntityTestCase {
.setAllowedTlds(ImmutableSet.of("dev", "app"))
.setAllowedClientIds(ImmutableSet.of("TheRegistrar, NewRegistrar"))
.setDiscountFraction(0.5)
.setDiscountPremiums(true)
.setDiscountYears(3)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, NOT_STARTED)
@@ -155,7 +157,7 @@ class AllocationTokenTest extends EntityTestCase {
}
@Test
void testBuild_invalidTLD() {
void testBuild_invalidTld() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
@@ -268,6 +270,50 @@ class AllocationTokenTest extends EntityTestCase {
assertTerminal(CANCELLED);
}
@Test
void testSetDiscountFractionTooHigh() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> new AllocationToken.Builder().setDiscountFraction(1.1));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Discount fraction must be between 0 and 1 inclusive");
}
@Test
void testSetDiscountFractionTooLow() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> new AllocationToken.Builder().setDiscountFraction(-.0001));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Discount fraction must be between 0 and 1 inclusive");
}
@Test
void testSetDiscountYearsTooHigh() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> new AllocationToken.Builder().setDiscountYears(11));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Discount years must be between 1 and 10 inclusive");
}
@Test
void testSetDiscountYearsTooLow() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> new AllocationToken.Builder().setDiscountYears(0));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Discount years must be between 1 and 10 inclusive");
}
@Test
void testBuild_noTokenType() {
IllegalArgumentException thrown =
@@ -295,6 +341,38 @@ class AllocationTokenTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().isEqualTo("Token must not be blank");
}
@Test
void testBuild_discountPremiumsRequiresDiscountFraction() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
new AllocationToken.Builder()
.setToken("abc")
.setTokenType(SINGLE_USE)
.setDiscountPremiums(true)
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Discount premiums can only be specified along with a discount fraction");
}
@Test
void testBuild_discountYearsRequiresDiscountFraction() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
new AllocationToken.Builder()
.setToken("abc")
.setTokenType(SINGLE_USE)
.setDiscountYears(2)
.build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Discount years can only be specified along with a discount fraction");
}
private void assertBadInitialTransition(TokenStatus status) {
assertBadTransition(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
@@ -69,9 +69,11 @@ public class ContactHistoryTest extends EntityTestCase {
}
static void assertContactHistoriesEqual(ContactHistory one, ContactHistory two) {
assertAboutImmutableObjects().that(one)
assertAboutImmutableObjects()
.that(one)
.isEqualExceptFields(two, "contactBase", "contactRepoId", "parent");
assertAboutImmutableObjects().that(one.getContactBase())
assertAboutImmutableObjects()
.that(one.getContactBase())
.isEqualExceptFields(two.getContactBase(), "repoId");
}
}
@@ -83,7 +83,8 @@ public class DomainHistoryTest extends EntityTestCase {
}
static void assertDomainHistoriesEqual(DomainHistory one, DomainHistory two) {
assertAboutImmutableObjects().that(one)
assertAboutImmutableObjects()
.that(one)
.isEqualExceptFields(two, "domainContent", "domainRepoId", "parent");
}
}
@@ -278,6 +278,7 @@ public class DomainBaseToXjcConverterTest {
ImmutableSet.of(
GracePeriod.forBillingEvent(
GracePeriodStatus.RENEW,
domain.getRepoId(),
persistResource(
new BillingEvent.OneTime.Builder()
.setReason(Reason.RENEW)
@@ -291,6 +292,7 @@ public class DomainBaseToXjcConverterTest {
.build())),
GracePeriod.create(
GracePeriodStatus.TRANSFER,
domain.getRepoId(),
DateTime.parse("1920-01-01T00:00:00Z"),
"foo",
null)))
@@ -122,6 +122,7 @@ final class RdeFixtures {
ImmutableSet.of(
GracePeriod.forBillingEvent(
GracePeriodStatus.RENEW,
domain.getRepoId(),
persistResource(
new BillingEvent.OneTime.Builder()
.setReason(Reason.RENEW)
@@ -135,6 +136,7 @@ final class RdeFixtures {
.build())),
GracePeriod.create(
GracePeriodStatus.TRANSFER,
domain.getRepoId(),
DateTime.parse("1992-01-01T00:00:00Z"),
"foo",
null)))
@@ -26,6 +26,8 @@ import static google.registry.config.RegistryConfig.getContactAndHostRoidSuffix;
import static google.registry.config.RegistryConfig.getContactAutomaticTransferLength;
import static google.registry.model.EppResourceUtils.createDomainRepoId;
import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.ImmutableObjectSubject.immutableObjectCorrespondence;
import static google.registry.model.ResourceTransferUtils.createTransferResponse;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.registry.Registry.TldState.GENERAL_AVAILABILITY;
@@ -51,7 +53,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import com.google.common.net.InetAddresses;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.cmd.Saver;
@@ -71,6 +72,7 @@ import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid;
@@ -100,7 +102,6 @@ import google.registry.tmch.LordnTaskUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nullable;
import org.joda.money.Money;
import org.joda.time.DateTime;
@@ -502,9 +503,10 @@ public class DatastoreHelper {
DateTime creationTime,
DateTime expirationTime) {
String domainName = String.format("%s.%s", label, tld);
String repoId = generateNewDomainRoid(tld);
DomainBase domain =
new DomainBase.Builder()
.setRepoId(generateNewDomainRoid(tld))
.setRepoId(repoId)
.setDomainName(domainName)
.setPersistedCurrentSponsorClientId("TheRegistrar")
.setCreationClientId("TheRegistrar")
@@ -517,7 +519,7 @@ public class DatastoreHelper {
DesignatedContact.create(Type.TECH, contact.createVKey())))
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("fooBAR")))
.addGracePeriod(
GracePeriod.create(GracePeriodStatus.ADD, now.plusDays(10), "foo", null))
GracePeriod.create(GracePeriodStatus.ADD, repoId, now.plusDays(10), "foo", null))
.build();
HistoryEntry historyEntryDomainCreate =
persistResource(
@@ -685,20 +687,15 @@ public class DatastoreHelper {
/** Assert that the actual billing event matches the expected one, ignoring IDs. */
public static void assertBillingEventsEqual(BillingEvent actual, BillingEvent expected) {
assertThat(stripBillingEventId(actual)).isEqualTo(stripBillingEventId(expected));
assertAboutImmutableObjects().that(actual).isEqualExceptFields(expected, "id");
}
/** Assert that the actual billing events match the expected ones, ignoring IDs and order. */
public static void assertBillingEventsEqual(
Iterable<BillingEvent> actual, Iterable<BillingEvent> expected) {
assertThat(
Streams.stream(actual)
.map(DatastoreHelper::stripBillingEventId)
.collect(toImmutableList()))
.containsExactlyElementsIn(
Streams.stream(expected)
.map(DatastoreHelper::stripBillingEventId)
.collect(toImmutableList()));
assertThat(actual)
.comparingElementsUsing(immutableObjectCorrespondence("id"))
.containsExactlyElementsIn(expected);
}
/** Assert that the expected billing events are exactly the ones found in the fake Datastore. */
@@ -716,14 +713,7 @@ public class DatastoreHelper {
*/
public static void assertBillingEventsForResource(
EppResource resource, BillingEvent... expected) {
assertThat(
Streams.stream(getBillingEvents(resource))
.map(DatastoreHelper::stripBillingEventId)
.collect(toImmutableList()))
.containsExactlyElementsIn(
Arrays.stream(expected)
.map(DatastoreHelper::stripBillingEventId)
.collect(toImmutableList()));
assertBillingEventsEqual(getBillingEvents(resource), Arrays.asList(expected));
}
/** Assert that there are no billing events. */
@@ -731,74 +721,71 @@ public class DatastoreHelper {
assertThat(getBillingEvents()).isEmpty();
}
/** Strips the billing event ID (really, sets it to a constant value) to facilitate comparison. */
/**
* Strips the billing event ID (really, sets it to a constant value) to facilitate comparison.
*
* <p>Note: Prefer {@link #assertPollMessagesEqual} when that is suitable.
*/
public static BillingEvent stripBillingEventId(BillingEvent billingEvent) {
return billingEvent.asBuilder().setId(1L).build();
}
/** Assert that the actual poll message matches the expected one, ignoring IDs. */
public static void assertPollMessagesEqual(PollMessage actual, PollMessage expected) {
assertThat(POLL_MESSAGE_ID_STRIPPER.apply(actual))
.isEqualTo(POLL_MESSAGE_ID_STRIPPER.apply(expected));
assertAboutImmutableObjects().that(actual).isEqualExceptFields(expected, "id");
}
/** Assert that the actual poll messages match the expected ones, ignoring IDs and order. */
public static void assertPollMessagesEqual(
Iterable<PollMessage> actual, Iterable<PollMessage> expected) {
assertThat(Streams.stream(actual).map(POLL_MESSAGE_ID_STRIPPER).collect(toImmutableList()))
.containsExactlyElementsIn(
Streams.stream(expected).map(POLL_MESSAGE_ID_STRIPPER).collect(toImmutableList()));
assertThat(actual)
.comparingElementsUsing(immutableObjectCorrespondence("id"))
.containsExactlyElementsIn(expected);
}
public static void assertPollMessages(String clientId, PollMessage... expected) {
assertPollMessagesEqual(getPollMessages(clientId), Arrays.asList(expected));
}
public static void assertPollMessages(PollMessage... expected) {
assertPollMessagesEqual(getPollMessages(), Arrays.asList(expected));
}
public static void assertPollMessagesForResource(EppResource resource, PollMessage... expected) {
assertThat(
getPollMessages(resource)
.stream()
.map(POLL_MESSAGE_ID_STRIPPER)
.collect(toImmutableList()))
.containsExactlyElementsIn(
Arrays.stream(expected).map(POLL_MESSAGE_ID_STRIPPER).collect(toImmutableList()));
assertPollMessagesEqual(getPollMessages(resource), Arrays.asList(expected));
}
/** Helper to effectively erase the poll message ID to facilitate comparison. */
public static final Function<PollMessage, PollMessage> POLL_MESSAGE_ID_STRIPPER =
pollMessage -> pollMessage.asBuilder().setId(1L).build();
public static ImmutableList<PollMessage> getPollMessages() {
return Streams.stream(ofy().load().type(PollMessage.class)).collect(toImmutableList());
return ImmutableList.copyOf(ofy().load().type(PollMessage.class));
}
public static ImmutableList<PollMessage> getPollMessages(String clientId) {
return Streams.stream(ofy().load().type(PollMessage.class).filter("clientId", clientId))
.collect(toImmutableList());
return ImmutableList.copyOf(ofy().load().type(PollMessage.class).filter("clientId", clientId));
}
public static ImmutableList<PollMessage> getPollMessages(EppResource resource) {
return Streams.stream(ofy().load().type(PollMessage.class).ancestor(resource))
.collect(toImmutableList());
return ImmutableList.copyOf(ofy().load().type(PollMessage.class).ancestor(resource));
}
public static ImmutableList<PollMessage> getPollMessages(String clientId, DateTime now) {
return Streams.stream(
ofy()
.load()
.type(PollMessage.class)
.filter("clientId", clientId)
.filter("eventTime <=", now.toDate()))
.collect(toImmutableList());
return ImmutableList.copyOf(
ofy()
.load()
.type(PollMessage.class)
.filter("clientId", clientId)
.filter("eventTime <=", now.toDate()));
}
/** Gets all PollMessages associated with the given EppResource. */
public static ImmutableList<PollMessage> getPollMessages(
EppResource resource, String clientId, DateTime now) {
return Streams.stream(
ofy()
.load()
.type(PollMessage.class)
.ancestor(resource)
.filter("clientId", clientId)
.filter("eventTime <=", now.toDate()))
.collect(toImmutableList());
return ImmutableList.copyOf(
ofy()
.load()
.type(PollMessage.class)
.ancestor(resource)
.filter("clientId", clientId)
.filter("eventTime <=", now.toDate()));
}
public static PollMessage getOnlyPollMessage(String clientId) {
@@ -832,6 +819,12 @@ public class DatastoreHelper {
.collect(onlyElement());
}
public static void assertAllocationTokens(AllocationToken... expectedTokens) {
assertThat(ofy().load().type(AllocationToken.class).list())
.comparingElementsUsing(immutableObjectCorrespondence("updateTimestamp", "creationTime"))
.containsExactlyElementsIn(expectedTokens);
}
/** Returns a newly allocated, globally unique domain repoId of the format HEX-TLD. */
public static String generateNewDomainRoid(String tld) {
return createDomainRepoId(ObjectifyService.allocateId(), tld);
@@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.testing.DatastoreHelper.assertAllocationTokens;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@@ -32,11 +33,9 @@ import static org.mockito.Mockito.verify;
import com.beust.jcommander.ParameterException;
import com.google.appengine.tools.remoteapi.RemoteApiException;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.io.Files;
import com.googlecode.objectify.Key;
import google.registry.model.domain.token.AllocationToken;
@@ -160,6 +159,8 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
"--allowed_client_ids", "TheRegistrar,NewRegistrar",
"--allowed_tlds", "tld,example",
"--discount_fraction", "0.5",
"--discount_premiums", "true",
"--discount_years", "6",
"--token_status_transitions",
String.format(
"\"%s=NOT_STARTED,%s=VALID,%s=ENDED\"", START_OF_TIME, promoStart, promoEnd));
@@ -170,6 +171,8 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.setAllowedClientIds(ImmutableSet.of("TheRegistrar", "NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld", "example"))
.setDiscountFraction(0.5)
.setDiscountPremiums(true)
.setDiscountYears(6)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, TokenStatus.NOT_STARTED)
@@ -309,26 +312,6 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.isEqualTo("For UNLIMITED_USE tokens, must specify --token_status_transitions");
}
private void assertAllocationTokens(AllocationToken... expectedTokens) {
// Using ImmutableObject comparison here is tricky because the creation/updated timestamps are
// neither easy nor valuable to test here.
ImmutableMap<String, AllocationToken> actualTokens =
Maps.uniqueIndex(ofy().load().type(AllocationToken.class), AllocationToken::getToken);
assertThat(actualTokens).hasSize(expectedTokens.length);
for (AllocationToken expectedToken : expectedTokens) {
AllocationToken match = actualTokens.get(expectedToken.getToken());
assertThat(match).isNotNull();
assertThat(match.getRedemptionHistoryEntry())
.isEqualTo(expectedToken.getRedemptionHistoryEntry());
assertThat(match.getAllowedClientIds()).isEqualTo(expectedToken.getAllowedClientIds());
assertThat(match.getAllowedTlds()).isEqualTo(expectedToken.getAllowedTlds());
assertThat(match.getDiscountFraction()).isEqualTo(expectedToken.getDiscountFraction());
assertThat(match.getTokenStatusTransitions())
.isEqualTo(expectedToken.getTokenStatusTransitions());
assertThat(match.getTokenType()).isEqualTo(expectedToken.getTokenType());
}
}
private AllocationToken createToken(
String token,
@Nullable Key<HistoryEntry> redemptionHistoryEntry,
@@ -77,6 +77,24 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
assertThat(reloadResource(token).getDiscountFraction()).isEqualTo(0.15);
}
@Test
void testUpdateDiscountPremiums() throws Exception {
AllocationToken token =
persistResource(
builderWithPromo().setDiscountFraction(0.5).setDiscountPremiums(false).build());
runCommandForced("--prefix", "token", "--discount_premiums", "true");
assertThat(reloadResource(token).shouldDiscountPremiums()).isTrue();
runCommandForced("--prefix", "token", "--discount_premiums", "false");
assertThat(reloadResource(token).shouldDiscountPremiums()).isFalse();
}
@Test
void testUpdateDiscountYears() throws Exception {
AllocationToken token = persistResource(builderWithPromo().setDiscountFraction(0.5).build());
runCommandForced("--prefix", "token", "--discount_years", "4");
assertThat(reloadResource(token).getDiscountYears()).isEqualTo(4);
}
@Test
void testUpdateStatusTransitions() throws Exception {
DateTime now = DateTime.now(UTC);
@@ -228,11 +228,12 @@ class DomainWhoisResponseTest {
VKey<ContactResource> adminResourceKey = adminContact.createVKey();
VKey<ContactResource> techResourceKey = techContact.createVKey();
String repoId = "3-TLD";
domainBase =
persistResource(
new DomainBase.Builder()
.setDomainName("example.tld")
.setRepoId("3-TLD")
.setRepoId(repoId)
.setLastEppUpdateTime(DateTime.parse("2009-05-29T20:13:00Z"))
.setCreationTimeForTest(DateTime.parse("2000-10-08T00:45:00Z"))
.setRegistrationExpirationTime(DateTime.parse("2010-10-08T00:44:59Z"))
@@ -252,8 +253,9 @@ class DomainWhoisResponseTest {
.setDsData(ImmutableSet.of(DelegationSignerData.create(1, 2, 3, "deadface")))
.setGracePeriods(
ImmutableSet.of(
GracePeriod.create(GracePeriodStatus.ADD, END_OF_TIME, "", null),
GracePeriod.create(GracePeriodStatus.TRANSFER, END_OF_TIME, "", null)))
GracePeriod.create(GracePeriodStatus.ADD, repoId, END_OF_TIME, "", null),
GracePeriod.create(
GracePeriodStatus.TRANSFER, repoId, END_OF_TIME, "", null)))
.build());
}
@@ -0,0 +1,39 @@
<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>%DOMAIN%</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:fee-0.6">
<fee:domain>
<fee:name>%DOMAIN%</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
<fee:domain>
<fee:name>%DOMAIN%</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">2</fee:period>
</fee:domain>
<fee:domain>
<fee:name>%DOMAIN%</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">5</fee:period>
</fee:domain>
</fee:check>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns:launch="urn:ietf:params:xml:ns:launch-1.0" xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1" xmlns:host="urn:ietf:params:xml:ns:host-1.0" xmlns:fee11="urn:ietf:params:xml:ns:fee-0.11" xmlns:fee12="urn:ietf:params:xml:ns:fee-0.12" xmlns:fee="urn:ietf:params:xml:ns:fee-0.6" xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0" xmlns="urn:ietf:params:xml:ns:epp-1.0" xmlns:domain="urn:ietf:params:xml:ns:domain-1.0" xmlns:contact="urn:ietf:params:xml:ns:contact-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:chkData>
<domain:cd>
<domain:name avail="true">%DOMAIN%</domain:name>
</domain:cd>
</domain:chkData>
</resData>
<extension>
<fee:chkData>
<fee:cd>
<fee:name>%DOMAIN%</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="create">%COST_1YR%</fee:fee>
%FEE_CLASS%
</fee:cd>
<fee:cd>
<fee:name>%DOMAIN%</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">2</fee:period>
<fee:fee description="create">%COST_2YR%</fee:fee>
%FEE_CLASS%
</fee:cd>
<fee:cd>
<fee:name>%DOMAIN%</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">5</fee:period>
<fee:fee description="create">%COST_5YR%</fee:fee>
%FEE_CLASS%
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -4,7 +4,7 @@
<domain:create
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>%DOMAIN%</domain:name>
<domain:period unit="y">2</domain:period>
<domain:period unit="y">%YEARS%</domain:period>
<domain:ns>
<domain:hostObj>ns1.example.net</domain:hostObj>
<domain:hostObj>ns2.example.net</domain:hostObj>
@@ -4,7 +4,7 @@
<domain:create
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>rich.example</domain:name>
<domain:period unit="y">2</domain:period>
<domain:period unit="y">%YEARS%</domain:period>
<domain:ns>
<domain:hostObj>ns1.example.net</domain:hostObj>
<domain:hostObj>ns2.example.net</domain:hostObj>
@@ -25,7 +25,7 @@
</allocationToken:allocationToken>
<fee:create xmlns:fee="urn:ietf:params:xml:ns:fee-0.12">
<fee:currency>USD</fee:currency>
<fee:fee>193.5</fee:fee>
<fee:fee>%FEE%</fee:fee>
</fee:create>
</extension>
<clTRID>ABC-12345</clTRID>
@@ -8,13 +8,13 @@
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>rich.example</domain:name>
<domain:crDate>1999-04-03T22:00:00.0Z</domain:crDate>
<domain:exDate>2001-04-03T22:00:00.0Z</domain:exDate>
<domain:exDate>%EXDATE%</domain:exDate>
</domain:creData>
</resData>
<extension>
<fee:creData xmlns:fee="urn:ietf:params:xml:ns:fee-0.12">
<fee:currency>USD</fee:currency>
<fee:fee description="create">200.00</fee:fee>
<fee:fee description="create">%FEE%</fee:fee>
</fee:creData>
</extension>
<trID>
@@ -0,0 +1,19 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:creData
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>%DOMAIN%</domain:name>
<domain:crDate>%CRDATE%</domain:crDate>
<domain:exDate>%EXDATE%</domain:exDate>
</domain:creData>
</resData>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>
@@ -231,12 +231,14 @@ class google.registry.model.domain.secdns.DelegationSignerData {
}
class google.registry.model.domain.token.AllocationToken {
@Id java.lang.String token;
boolean discountPremiums;
com.googlecode.objectify.Key<google.registry.model.reporting.HistoryEntry> redemptionHistoryEntry;
double discountFraction;
google.registry.model.CreateAutoTimestamp creationTime;
google.registry.model.UpdateAutoTimestamp updateTimestamp;
google.registry.model.common.TimedTransitionProperty<google.registry.model.domain.token.AllocationToken$TokenStatus, google.registry.model.domain.token.AllocationToken$TokenStatusTransition> tokenStatusTransitions;
google.registry.model.domain.token.AllocationToken$TokenType tokenType;
int discountYears;
java.lang.String domainName;
java.util.Set<java.lang.String> allowedClientIds;
java.util.Set<java.lang.String> allowedTlds;
@@ -0,0 +1,41 @@
-- Copyright 2020 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.
create table "GracePeriod" (
id bigserial not null,
billing_event_id int8,
billing_recurrence_id int8,
registrar_id text not null,
domain_repo_id text not null,
expiration_time timestamptz not null,
type text not null,
primary key (id)
);
alter table if exists "GracePeriod"
add constraint FK2mys4hojm6ev2g9tmy5aq6m7g
foreign key (domain_repo_id)
references "Domain";
alter table if exists "GracePeriod"
add constraint fk_grace_period_billing_event_id
foreign key (billing_event_id)
references "BillingEvent";
alter table if exists "GracePeriod"
add constraint fk_grace_period_billing_recurrence_id
foreign key (billing_recurrence_id)
references "BillingRecurrence";
create index IDXj1mtx98ndgbtb1bkekahms18w on "GracePeriod" (domain_repo_id);
@@ -339,11 +339,12 @@ create sequence history_id_sequence start 1 increment 1;
create table "GracePeriod" (
id bigserial not null,
billing_event_one_time int8,
billing_event_recurring int8,
registrar_id text,
expiration_time timestamptz,
type int4,
billing_event_id int8,
billing_recurrence_id int8,
registrar_id text not null,
domain_repo_id text not null,
expiration_time timestamptz not null,
type text not null,
primary key (id)
);
@@ -596,6 +597,7 @@ create index IDXrh4xmrot9bd63o382ow9ltfig on "DomainHistory" (creation_time);
create index IDXaro1omfuaxjwmotk3vo00trwm on "DomainHistory" (history_registrar_id);
create index IDXsu1nam10cjes9keobapn5jvxj on "DomainHistory" (history_type);
create index IDX6w3qbtgce93cal2orjg1tw7b7 on "DomainHistory" (history_modification_time);
create index IDXj1mtx98ndgbtb1bkekahms18w on "GracePeriod" (domain_repo_id);
create index IDXfg2nnjlujxo6cb9fha971bq2n on "HostHistory" (creation_time);
create index IDX1iy7njgb7wjmj9piml4l2g0qi on "HostHistory" (history_registrar_id);
create index IDXkkwbwcwvrdkkqothkiye4jiff on "HostHistory" (host_name);
@@ -632,6 +634,11 @@ create index spec11threatmatch_check_date_idx on "Spec11ThreatMatch" (check_date
foreign key (domain_repo_id)
references "Domain";
alter table if exists "GracePeriod"
add constraint FK2mys4hojm6ev2g9tmy5aq6m7g
foreign key (domain_repo_id)
references "Domain";
alter table if exists "PremiumEntry"
add constraint FKo0gw90lpo1tuee56l0nb6y6g5
foreign key (revision_id)
@@ -488,6 +488,40 @@ CREATE TABLE public."DomainHost" (
);
--
-- Name: GracePeriod; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."GracePeriod" (
id bigint NOT NULL,
billing_event_id bigint,
billing_recurrence_id bigint,
registrar_id text NOT NULL,
domain_repo_id text NOT NULL,
expiration_time timestamp with time zone NOT NULL,
type text NOT NULL
);
--
-- Name: GracePeriod_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public."GracePeriod_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: GracePeriod_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public."GracePeriod_id_seq" OWNED BY public."GracePeriod".id;
--
-- Name: HostHistory; Type: TABLE; Schema: public; Owner: -
--
@@ -900,6 +934,13 @@ ALTER TABLE ONLY public."BillingRecurrence" ALTER COLUMN billing_recurrence_id S
ALTER TABLE ONLY public."ClaimsList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ClaimsList_revision_id_seq"'::regclass);
--
-- Name: GracePeriod id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."GracePeriod" ALTER COLUMN id SET DEFAULT nextval('public."GracePeriod_id_seq"'::regclass);
--
-- Name: PollMessage poll_message_id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -1022,6 +1063,14 @@ ALTER TABLE ONLY public."Domain"
ADD CONSTRAINT "Domain_pkey" PRIMARY KEY (repo_id);
--
-- Name: GracePeriod GracePeriod_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."GracePeriod"
ADD CONSTRAINT "GracePeriod_pkey" PRIMARY KEY (id);
--
-- Name: HostHistory HostHistory_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -1310,6 +1359,13 @@ CREATE INDEX idxhmv411mdqo5ibn4vy7ykxpmlv ON public."BillingEvent" USING btree (
CREATE INDEX idxhp33wybmb6tbpr1bq7ttwk8je ON public."ContactHistory" USING btree (history_registrar_id);
--
-- Name: idxj1mtx98ndgbtb1bkekahms18w; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idxj1mtx98ndgbtb1bkekahms18w ON public."GracePeriod" USING btree (domain_repo_id);
--
-- Name: idxj77pfwhui9f0i7wjq6lmibovj; Type: INDEX; Schema: public; Owner: -
--
@@ -1488,6 +1544,14 @@ ALTER TABLE ONLY public."RegistryLock"
ADD CONSTRAINT fk2lhcwpxlnqijr96irylrh1707 FOREIGN KEY (relock_revision_id) REFERENCES public."RegistryLock"(revision_id);
--
-- Name: GracePeriod fk2mys4hojm6ev2g9tmy5aq6m7g; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."GracePeriod"
ADD CONSTRAINT fk2mys4hojm6ev2g9tmy5aq6m7g FOREIGN KEY (domain_repo_id) REFERENCES public."Domain"(repo_id);
--
-- Name: Domain fk2u3srsfbei272093m3b3xwj23; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -1728,6 +1792,22 @@ ALTER TABLE ONLY public."DomainHost"
ADD CONSTRAINT fk_domainhost_host_valid FOREIGN KEY (host_repo_id) REFERENCES public."HostResource"(repo_id);
--
-- Name: GracePeriod fk_grace_period_billing_event_id; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."GracePeriod"
ADD CONSTRAINT fk_grace_period_billing_event_id FOREIGN KEY (billing_event_id) REFERENCES public."BillingEvent"(billing_event_id);
--
-- Name: GracePeriod fk_grace_period_billing_recurrence_id; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."GracePeriod"
ADD CONSTRAINT fk_grace_period_billing_recurrence_id FOREIGN KEY (billing_recurrence_id) REFERENCES public."BillingRecurrence"(billing_recurrence_id);
--
-- Name: HostResource fk_host_resource_superordinate_domain; Type: FK CONSTRAINT; Schema: public; Owner: -
--
+5 -9
View File
@@ -21,15 +21,11 @@ apt-get install apt-utils gnupg -y
apt-get upgrade -y
# Install Java
apt-get install openjdk-11-jdk-headless -y
# Install nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# Install node
nvm install node
# Install npm
nvm install-latest-npm
# Install Python
apt-get install python -y
# Install Node
curl -sL https://deb.nodesource.com/setup_current.x | bash -
apt-get install -y nodejs
# Install gcloud
# Cribbed from https://cloud.google.com/sdk/docs/quickstart-debian-ubuntu
apt-get install lsb-release -y
+17 -23
View File
@@ -37,30 +37,24 @@ steps:
cat tool-credential.json.enc | base64 -d | gcloud kms decrypt \
--ciphertext-file=- --plaintext-file=tool-credential.json \
--location=global --keyring=nomulus-tool-keyring --key=nomulus-tool-key
# Set the path to the file for sql_access_info to deploy the Spec 11 pipeline
- name: 'gcr.io/$PROJECT_ID/builder:latest'
entrypoint: /bin/bash
args:
- -c
- |
set -e
if [ ${_ENV} == production ]; then
echo "gs://domain-registry-beam/cloudsql/admin_credential.enc" > sql_access_path.txt
else
echo "gs://domain-registry-${_ENV}-beam/cloudsql/admin_credential.enc" > sql_access_path.txt
fi
# Deploy the Spec11 pipeline to GCS.
- name: 'gcr.io/$PROJECT_ID/nomulus-tool:latest'
args:
- -e
- ${_ENV}
- --credential
- tool-credential.json
- --sql_access_info
- `cat sql_access_path.txt`
- deploy_spec11_pipeline
- --project
- $PROJECT_ID
#- name: 'gcr.io/$PROJECT_ID/builder:latest'
# entrypoint: /bin/bash
# args:
# - -c
# - |
# set -e
# if [ ${_ENV} == production ]; then
# project_id="domain-registry"
# else
# project_id="domain-registry-${_ENV}"
# fi
# echo "gs://$${project_id}-beam/cloudsql/admin_credential.enc"
# gsutil cp gs://$PROJECT_ID-deploy/${TAG_NAME}/nomulus.jar .
# java -jar nomulus.jar -e ${_ENV} --credential tool-credential.json \
# --sql_access_info \
# "gs://$${project_id}-beam/cloudsql/admin_credential.enc" \
# deploy_spec11_pipeline --project $${project_id}
# Deploy the invoicing pipeline to GCS.
- name: 'gcr.io/$PROJECT_ID/nomulus-tool:latest'
args:
+10 -10
View File
@@ -20,16 +20,16 @@ steps:
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
args: ['mkdir', 'nomulus']
# Run tests
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
# Set home for Gradle caches. Must be consistent with last step below
# and ./build_nomulus_for_env.sh
env: [ 'GRADLE_USER_HOME=./cloudbuild-caches' ]
args: ['./gradlew',
'test',
'-PskipDockerIncompatibleTests=true',
'-PmavenUrl=gcs://domain-registry-maven-repository/maven',
'-PpluginsUrl=gcs://domain-registry-maven-repository/plugins'
]
#- name: 'gcr.io/${PROJECT_ID}/builder:latest'
# # Set home for Gradle caches. Must be consistent with last step below
# # and ./build_nomulus_for_env.sh
# env: [ 'GRADLE_USER_HOME=./cloudbuild-caches' ]
# args: ['./gradlew',
# 'test',
# '-PskipDockerIncompatibleTests=true',
# '-PmavenUrl=gcs://domain-registry-maven-repository/maven',
# '-PpluginsUrl=gcs://domain-registry-maven-repository/plugins'
# ]
# Build the tool binary and image.
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
args: ['release/build_nomulus_for_env.sh', 'tool', 'output']
@@ -75,38 +75,39 @@ public class CollectionUtils {
}
/** Defensive copy helper for {@link Set}. */
public static <V> ImmutableSet<V> nullSafeImmutableCopy(Set<V> data) {
public static <V> ImmutableSet<V> nullSafeImmutableCopy(@Nullable Set<V> data) {
return data == null ? null : ImmutableSet.copyOf(data);
}
/** Defensive copy helper for {@link List}. */
public static <V> ImmutableList<V> nullSafeImmutableCopy(List<V> data) {
public static <V> ImmutableList<V> nullSafeImmutableCopy(@Nullable List<V> data) {
return data == null ? null : ImmutableList.copyOf(data);
}
/** Defensive copy helper for {@link Set}. */
public static <V> ImmutableSet<V> nullToEmptyImmutableCopy(Set<V> data) {
public static <V> ImmutableSet<V> nullToEmptyImmutableCopy(@Nullable Set<V> data) {
return data == null ? ImmutableSet.of() : ImmutableSet.copyOf(data);
}
/** Defensive copy helper for {@link Set}. */
public static <V extends Comparable<V>>
ImmutableSortedSet<V> nullToEmptyImmutableSortedCopy(Set<V> data) {
public static <V extends Comparable<V>> ImmutableSortedSet<V> nullToEmptyImmutableSortedCopy(
@Nullable Set<V> data) {
return data == null ? ImmutableSortedSet.of() : ImmutableSortedSet.copyOf(data);
}
/** Defensive copy helper for {@link SortedMap}. */
public static <K, V> ImmutableSortedMap<K, V> nullToEmptyImmutableCopy(SortedMap<K, V> data) {
public static <K, V> ImmutableSortedMap<K, V> nullToEmptyImmutableCopy(
@Nullable SortedMap<K, V> data) {
return data == null ? ImmutableSortedMap.of() : ImmutableSortedMap.copyOfSorted(data);
}
/** Defensive copy helper for {@link List}. */
public static <V> ImmutableList<V> nullToEmptyImmutableCopy(List<V> data) {
public static <V> ImmutableList<V> nullToEmptyImmutableCopy(@Nullable List<V> data) {
return data == null ? ImmutableList.of() : ImmutableList.copyOf(data);
}
/** Defensive copy helper for {@link Map}. */
public static <K, V> ImmutableMap<K, V> nullToEmptyImmutableCopy(Map<K, V> data) {
public static <K, V> ImmutableMap<K, V> nullToEmptyImmutableCopy(@Nullable Map<K, V> data) {
return data == null ? ImmutableMap.of() : ImmutableMap.copyOf(data);
}
@@ -29,33 +29,36 @@ public class PreconditionsUtils {
* preferable to throw an IAE instead of an NPE, such as where we want an IAE to indicate that
* it's just a bad argument/parameter and reserve NPEs for bugs and unexpected null values.
*/
public static <T> T checkArgumentNotNull(T reference) {
public static <T> T checkArgumentNotNull(@Nullable T reference) {
checkArgument(reference != null);
return reference;
}
/** Checks whether the provided reference is null, throws IAE if it is, and returns it if not. */
public static <T> T checkArgumentNotNull(T reference, @Nullable Object errorMessage) {
public static <T> T checkArgumentNotNull(@Nullable T reference, @Nullable Object errorMessage) {
checkArgument(reference != null, errorMessage);
return reference;
}
/** Checks whether the provided reference is null, throws IAE if it is, and returns it if not. */
public static <T> T checkArgumentNotNull(
T reference, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) {
@Nullable T reference,
@Nullable String errorMessageTemplate,
@Nullable Object... errorMessageArgs) {
checkArgument(reference != null, errorMessageTemplate, errorMessageArgs);
return reference;
}
/** Checks if the provided Optional is present, returns its value if so, and throws IAE if not. */
public static <T> T checkArgumentPresent(Optional<T> reference) {
public static <T> T checkArgumentPresent(@Nullable Optional<T> reference) {
checkArgumentNotNull(reference);
checkArgument(reference.isPresent());
return reference.get();
}
/** Checks if the provided Optional is present, returns its value if so, and throws IAE if not. */
public static <T> T checkArgumentPresent(Optional<T> reference, @Nullable Object errorMessage) {
public static <T> T checkArgumentPresent(
@Nullable Optional<T> reference, @Nullable Object errorMessage) {
checkArgumentNotNull(reference, errorMessage);
checkArgument(reference.isPresent(), errorMessage);
return reference.get();
@@ -63,7 +66,7 @@ public class PreconditionsUtils {
/** Checks if the provided Optional is present, returns its value if so, and throws IAE if not. */
public static <T> T checkArgumentPresent(
Optional<T> reference,
@Nullable Optional<T> reference,
@Nullable String errorMessageTemplate,
@Nullable Object... errorMessageArgs) {
checkArgumentNotNull(reference, errorMessageTemplate, errorMessageArgs);