1
0
mirror of https://github.com/google/nomulus synced 2026-05-25 01:01:57 +00:00

Compare commits

...

9 Commits

Author SHA1 Message Date
Ben McIlwain
739a15851d Remove a couple unnecessary inner transact() calls (#2124)
Also refactors a function to no longer unnecessarily return a low level Iterable
type.
2023-08-24 18:10:44 -04:00
sarahcaseybot
2c961b6283 Inject getTldCommand in RegistryToolComponent (#2123) 2023-08-24 16:56:40 -04:00
Weimin Yu
bcb2b2c784 Use Gmail for RegistryLock emails (#2122) 2023-08-24 15:18:47 -04:00
Lai Jiang
a91ed0f1ad Allow nested transactions when per-transaction isolation level is on (#2121)
It turns out that disallowing all nested transaction is impractical. So
in this PR we make it possible to run nested transactions (which are not
really nested as far as SQL is concerned, but rather lexically nested
calls to tm().transact() which will NOT open new transactions when
called within a transaction) as long as there is no conflict between the
specified isolation levels between the enclosing and the enclosed
transactions.

Note that this will change the behavior of calling tm().transact() with
no isolation level override, or a null override INSIDE a transaction.
The lack of the override will allow the nested transaction to run at
whatever level the enclosing transaction runs at, instead of at the
default level specified in the config file.
2023-08-24 14:35:59 -04:00
Weimin Yu
da28a2021c Use Gmail in Icann reporting actions (#2119) 2023-08-23 14:39:26 -04:00
Weimin Yu
ffd952a60e Fix Cloud Tasks retry failure (#2118)
* Fix Cloud Tasks failure to retry

Replace `SC_NOT_MODIFIED` (304) with `SC_SERVICE_UNAVAILABLE` (503) when
data is not available yet. Affected actions are invoice- and spec11-publishing.

It is confirmed that Cloud Tasks currently does not retry with code 304,
despite the public documentation stating so. We will use 503 for now,
pending the decision by Cloud Tasks whether to change behavior or
documentation.

The code `TOO_EARLY` (425) is another alternative. It is not meant for
our use case but at least sounds like it is. However, it is not in any
javax.servlet jar. We don't want to define our own constant, and we cannot upgrade
to jakarta.servlet yet.

Also revert previous mitigation.
2023-08-23 10:55:51 -04:00
gbrodman
97676d1a1f Add backend for editing whois-visible fields (#2100)
This includes a bit of refactoring of the GSON creation. There can exist
some objects (e.g. Address) where the JSON representation is not equal to the
representation that we store in the database. For these objects, when
deserializing, we should update the objects so that they reflect the
proper DB structure (indeed, this is already what we do for the XML
parsing of Address).
2023-08-22 16:40:02 -04:00
sarahcaseybot
1dcbc9e0cb Change PackagePromotion to BulkPricingPackage (#2096)
* Change PackagePromotion to BulkPricingPackage

* More name changes

* Fix some test names

* Change token type "BULK" to "BULK_PRICING"

* Fix missed token_type reference

* Add todo to remove package type
2023-08-22 16:39:24 -04:00
Lai Jiang
f59c387b9c Add the ability to specify per-transaction isolation level (#2104)
A config file field is added to control if per-transaction isolation
level is actually used. If set to true, nested transactions will throw
a runtime exception as the enclosing transaction may run at a different
isolation level.

In this PR we only add the ability to specify the isolation level,
without enabling it in any environment (including unit tests), or
actually specifying one for any query. This should allow us to set up
the system without impacting anything currently working.
2023-08-21 18:48:34 -04:00
80 changed files with 1829 additions and 1017 deletions

View File

@@ -0,0 +1,246 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.batch;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.BulkPricingPackage;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.auth.Auth;
import google.registry.ui.server.SendEmailUtils;
import google.registry.util.Clock;
import java.util.Optional;
import javax.inject.Inject;
import org.joda.time.Days;
/**
* An action that checks all {@link BulkPricingPackage} objects for compliance with their max create
* limit.
*/
@Action(
service = Service.BACKEND,
path = CheckBulkComplianceAction.PATH,
auth = Auth.AUTH_API_ADMIN)
public class CheckBulkComplianceAction implements Runnable {
public static final String PATH = "/_dr/task/checkBulkCompliance";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final SendEmailUtils sendEmailUtils;
private final Clock clock;
private final String bulkPricingPackageCreateLimitEmailSubject;
private final String bulkPricingPackageDomainLimitWarningEmailSubject;
private final String bulkPricingPackageDomainLimitUpgradeEmailSubject;
private final String bulkPricingPackageCreateLimitEmailBody;
private final String bulkPricingPackageDomainLimitWarningEmailBody;
private final String bulkPricingPackageDomainLimitUpgradeEmailBody;
private final String registrySupportEmail;
private static final int THIRTY_DAYS = 30;
private static final int FORTY_DAYS = 40;
@Inject
public CheckBulkComplianceAction(
SendEmailUtils sendEmailUtils,
Clock clock,
@Config("bulkPricingPackageCreateLimitEmailSubject")
String bulkPricingPackageCreateLimitEmailSubject,
@Config("bulkPricingPackageDomainLimitWarningEmailSubject")
String bulkPricingPackageDomainLimitWarningEmailSubject,
@Config("bulkPricingPackageDomainLimitUpgradeEmailSubject")
String bulkPricingPackageDomainLimitUpgradeEmailSubject,
@Config("bulkPricingPackageCreateLimitEmailBody")
String bulkPricingPackageCreateLimitEmailBody,
@Config("bulkPricingPackageDomainLimitWarningEmailBody")
String bulkPricingPackageDomainLimitWarningEmailBody,
@Config("bulkPricingPackageDomainLimitUpgradeEmailBody")
String bulkPricingPackageDomainLimitUpgradeEmailBody,
@Config("registrySupportEmail") String registrySupportEmail) {
this.sendEmailUtils = sendEmailUtils;
this.clock = clock;
this.bulkPricingPackageCreateLimitEmailSubject = bulkPricingPackageCreateLimitEmailSubject;
this.bulkPricingPackageDomainLimitWarningEmailSubject =
bulkPricingPackageDomainLimitWarningEmailSubject;
this.bulkPricingPackageDomainLimitUpgradeEmailSubject =
bulkPricingPackageDomainLimitUpgradeEmailSubject;
this.bulkPricingPackageCreateLimitEmailBody = bulkPricingPackageCreateLimitEmailBody;
this.bulkPricingPackageDomainLimitWarningEmailBody =
bulkPricingPackageDomainLimitWarningEmailBody;
this.bulkPricingPackageDomainLimitUpgradeEmailBody =
bulkPricingPackageDomainLimitUpgradeEmailBody;
this.registrySupportEmail = registrySupportEmail;
}
@Override
public void run() {
tm().transact(this::checkBulkPackages);
}
private void checkBulkPackages() {
ImmutableList<BulkPricingPackage> bulkPricingPackages =
tm().loadAllOf(BulkPricingPackage.class);
ImmutableMap.Builder<BulkPricingPackage, Long> bulkPricingPackagesOverCreateLimitBuilder =
new ImmutableMap.Builder<>();
ImmutableMap.Builder<BulkPricingPackage, Long>
bulkPricingPackagesOverActiveDomainsLimitBuilder = new ImmutableMap.Builder<>();
for (BulkPricingPackage bulkPricingPackage : bulkPricingPackages) {
Long creates =
(Long)
tm().query(
"SELECT COUNT(*) FROM DomainHistory WHERE current_package_token ="
+ " :token AND modificationTime >= :lastBilling AND type ="
+ " 'DOMAIN_CREATE'")
.setParameter("token", bulkPricingPackage.getToken().getKey().toString())
.setParameter(
"lastBilling", bulkPricingPackage.getNextBillingDate().minusYears(1))
.getSingleResult();
if (creates > bulkPricingPackage.getMaxCreates()) {
long overage = creates - bulkPricingPackage.getMaxCreates();
logger.atInfo().log(
"Bulk pricing package with bulk token %s has exceeded their max domain creation limit"
+ " by %d name(s).",
bulkPricingPackage.getToken().getKey(), overage);
bulkPricingPackagesOverCreateLimitBuilder.put(bulkPricingPackage, creates);
}
Long activeDomains =
tm().query(
"SELECT COUNT(*) FROM Domain WHERE currentBulkToken = :token"
+ " AND deletionTime = :endOfTime",
Long.class)
.setParameter("token", bulkPricingPackage.getToken())
.setParameter("endOfTime", END_OF_TIME)
.getSingleResult();
if (activeDomains > bulkPricingPackage.getMaxDomains()) {
int overage = Ints.saturatedCast(activeDomains) - bulkPricingPackage.getMaxDomains();
logger.atInfo().log(
"Bulk pricing package with bulk token %s has exceed their max active domains limit by"
+ " %d name(s).",
bulkPricingPackage.getToken().getKey(), overage);
bulkPricingPackagesOverActiveDomainsLimitBuilder.put(bulkPricingPackage, activeDomains);
}
}
handleBulkPricingPackageCreationOverage(bulkPricingPackagesOverCreateLimitBuilder.build());
handleActiveDomainOverage(bulkPricingPackagesOverActiveDomainsLimitBuilder.build());
}
private void handleBulkPricingPackageCreationOverage(
ImmutableMap<BulkPricingPackage, Long> overageList) {
if (overageList.isEmpty()) {
logger.atInfo().log("Found no bulk pricing packages over their create limit.");
return;
}
logger.atInfo().log(
"Found %d bulk pricing packages over their create limit.", overageList.size());
for (BulkPricingPackage bulkPricingPackage : overageList.keySet()) {
AllocationToken bulkToken = tm().loadByKey(bulkPricingPackage.getToken());
Optional<Registrar> registrar =
Registrar.loadByRegistrarIdCached(
Iterables.getOnlyElement(bulkToken.getAllowedRegistrarIds()));
if (registrar.isPresent()) {
String body =
String.format(
bulkPricingPackageCreateLimitEmailBody,
bulkPricingPackage.getId(),
bulkToken.getToken(),
registrar.get().getRegistrarName(),
bulkPricingPackage.getMaxCreates(),
overageList.get(bulkPricingPackage));
sendNotification(
bulkToken, bulkPricingPackageCreateLimitEmailSubject, body, registrar.get());
} else {
throw new IllegalStateException(
String.format("Could not find registrar for bulk token %s", bulkToken));
}
}
}
private void handleActiveDomainOverage(ImmutableMap<BulkPricingPackage, Long> overageList) {
if (overageList.isEmpty()) {
logger.atInfo().log("Found no bulk pricing packages over their active domains limit.");
return;
}
logger.atInfo().log(
"Found %d bulk pricing packages over their active domains limit.", overageList.size());
for (BulkPricingPackage bulkPricingPackage : overageList.keySet()) {
int daysSinceLastNotification =
bulkPricingPackage
.getLastNotificationSent()
.map(sentDate -> Days.daysBetween(sentDate, clock.nowUtc()).getDays())
.orElse(Integer.MAX_VALUE);
if (daysSinceLastNotification < THIRTY_DAYS) {
// Don't send an email if notification was already sent within the last 30
// days
continue;
} else if (daysSinceLastNotification < FORTY_DAYS) {
// Send an upgrade email if last email was between 30 and 40 days ago
sendActiveDomainOverageEmail(
/* warning= */ false, bulkPricingPackage, overageList.get(bulkPricingPackage));
} else {
// Send a warning email
sendActiveDomainOverageEmail(
/* warning= */ true, bulkPricingPackage, overageList.get(bulkPricingPackage));
}
}
}
private void sendActiveDomainOverageEmail(
boolean warning, BulkPricingPackage bulkPricingPackage, long activeDomains) {
String emailSubject =
warning
? bulkPricingPackageDomainLimitWarningEmailSubject
: bulkPricingPackageDomainLimitUpgradeEmailSubject;
String emailTemplate =
warning
? bulkPricingPackageDomainLimitWarningEmailBody
: bulkPricingPackageDomainLimitUpgradeEmailBody;
AllocationToken bulkToken = tm().loadByKey(bulkPricingPackage.getToken());
Optional<Registrar> registrar =
Registrar.loadByRegistrarIdCached(
Iterables.getOnlyElement(bulkToken.getAllowedRegistrarIds()));
if (registrar.isPresent()) {
String body =
String.format(
emailTemplate,
bulkPricingPackage.getId(),
bulkToken.getToken(),
registrar.get().getRegistrarName(),
bulkPricingPackage.getMaxDomains(),
activeDomains);
sendNotification(bulkToken, emailSubject, body, registrar.get());
tm().put(bulkPricingPackage.asBuilder().setLastNotificationSent(clock.nowUtc()).build());
} else {
throw new IllegalStateException(
String.format("Could not find registrar for bulk token %s", bulkToken));
}
}
private void sendNotification(
AllocationToken bulkToken, String subject, String body, Registrar registrar) {
logger.atInfo().log(
String.format(
"Compliance email sent to support regarding the %s registrar and the bulk pricing"
+ " package with token %s.",
registrar.getRegistrarName(), bulkToken.getToken()));
sendEmailUtils.sendEmail(subject, body, ImmutableList.of(registrySupportEmail));
}
}

View File

@@ -1,226 +0,0 @@
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.batch;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.PackagePromotion;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.Action.Service;
import google.registry.request.auth.Auth;
import google.registry.ui.server.SendEmailUtils;
import google.registry.util.Clock;
import java.util.Optional;
import javax.inject.Inject;
import org.joda.time.Days;
/**
* An action that checks all {@link PackagePromotion} objects for compliance with their max create
* limit.
*/
@Action(
service = Service.BACKEND,
path = CheckPackagesComplianceAction.PATH,
auth = Auth.AUTH_API_ADMIN)
public class CheckPackagesComplianceAction implements Runnable {
public static final String PATH = "/_dr/task/checkPackagesCompliance";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final SendEmailUtils sendEmailUtils;
private final Clock clock;
private final String packageCreateLimitEmailSubject;
private final String packageDomainLimitWarningEmailSubject;
private final String packageDomainLimitUpgradeEmailSubject;
private final String packageCreateLimitEmailBody;
private final String packageDomainLimitWarningEmailBody;
private final String packageDomainLimitUpgradeEmailBody;
private final String registrySupportEmail;
private static final int THIRTY_DAYS = 30;
private static final int FORTY_DAYS = 40;
@Inject
public CheckPackagesComplianceAction(
SendEmailUtils sendEmailUtils,
Clock clock,
@Config("packageCreateLimitEmailSubject") String packageCreateLimitEmailSubject,
@Config("packageDomainLimitWarningEmailSubject") String packageDomainLimitWarningEmailSubject,
@Config("packageDomainLimitUpgradeEmailSubject") String packageDomainLimitUpgradeEmailSubject,
@Config("packageCreateLimitEmailBody") String packageCreateLimitEmailBody,
@Config("packageDomainLimitWarningEmailBody") String packageDomainLimitWarningEmailBody,
@Config("packageDomainLimitUpgradeEmailBody") String packageDomainLimitUpgradeEmailBody,
@Config("registrySupportEmail") String registrySupportEmail) {
this.sendEmailUtils = sendEmailUtils;
this.clock = clock;
this.packageCreateLimitEmailSubject = packageCreateLimitEmailSubject;
this.packageDomainLimitWarningEmailSubject = packageDomainLimitWarningEmailSubject;
this.packageDomainLimitUpgradeEmailSubject = packageDomainLimitUpgradeEmailSubject;
this.packageCreateLimitEmailBody = packageCreateLimitEmailBody;
this.packageDomainLimitWarningEmailBody = packageDomainLimitWarningEmailBody;
this.packageDomainLimitUpgradeEmailBody = packageDomainLimitUpgradeEmailBody;
this.registrySupportEmail = registrySupportEmail;
}
@Override
public void run() {
tm().transact(this::checkPackages);
}
private void checkPackages() {
ImmutableList<PackagePromotion> packages = tm().loadAllOf(PackagePromotion.class);
ImmutableMap.Builder<PackagePromotion, Long> packagesOverCreateLimitBuilder =
new ImmutableMap.Builder<>();
ImmutableMap.Builder<PackagePromotion, Long> packagesOverActiveDomainsLimitBuilder =
new ImmutableMap.Builder<>();
for (PackagePromotion packagePromo : packages) {
Long creates =
(Long)
tm().query(
"SELECT COUNT(*) FROM DomainHistory WHERE current_package_token ="
+ " :token AND modificationTime >= :lastBilling AND type ="
+ " 'DOMAIN_CREATE'")
.setParameter("token", packagePromo.getToken().getKey().toString())
.setParameter("lastBilling", packagePromo.getNextBillingDate().minusYears(1))
.getSingleResult();
if (creates > packagePromo.getMaxCreates()) {
long overage = creates - packagePromo.getMaxCreates();
logger.atInfo().log(
"Package with package token %s has exceeded their max domain creation limit"
+ " by %d name(s).",
packagePromo.getToken().getKey(), overage);
packagesOverCreateLimitBuilder.put(packagePromo, creates);
}
Long activeDomains =
tm().query(
"SELECT COUNT(*) FROM Domain WHERE currentPackageToken = :token"
+ " AND deletionTime = :endOfTime",
Long.class)
.setParameter("token", packagePromo.getToken())
.setParameter("endOfTime", END_OF_TIME)
.getSingleResult();
if (activeDomains > packagePromo.getMaxDomains()) {
int overage = Ints.saturatedCast(activeDomains) - packagePromo.getMaxDomains();
logger.atInfo().log(
"Package with package token %s has exceed their max active domains limit by"
+ " %d name(s).",
packagePromo.getToken().getKey(), overage);
packagesOverActiveDomainsLimitBuilder.put(packagePromo, activeDomains);
}
}
handlePackageCreationOverage(packagesOverCreateLimitBuilder.build());
handleActiveDomainOverage(packagesOverActiveDomainsLimitBuilder.build());
}
private void handlePackageCreationOverage(ImmutableMap<PackagePromotion, Long> overageList) {
if (overageList.isEmpty()) {
logger.atInfo().log("Found no packages over their create limit.");
return;
}
logger.atInfo().log("Found %d packages over their create limit.", overageList.size());
for (PackagePromotion packagePromotion : overageList.keySet()) {
AllocationToken packageToken = tm().loadByKey(packagePromotion.getToken());
Optional<Registrar> registrar =
Registrar.loadByRegistrarIdCached(
Iterables.getOnlyElement(packageToken.getAllowedRegistrarIds()));
if (registrar.isPresent()) {
String body =
String.format(
packageCreateLimitEmailBody,
packagePromotion.getId(),
packageToken.getToken(),
registrar.get().getRegistrarName(),
packagePromotion.getMaxCreates(),
overageList.get(packagePromotion));
sendNotification(packageToken, packageCreateLimitEmailSubject, body, registrar.get());
} else {
throw new IllegalStateException(
String.format("Could not find registrar for package token %s", packageToken));
}
}
}
private void handleActiveDomainOverage(ImmutableMap<PackagePromotion, Long> overageList) {
if (overageList.isEmpty()) {
logger.atInfo().log("Found no packages over their active domains limit.");
return;
}
logger.atInfo().log("Found %d packages over their active domains limit.", overageList.size());
for (PackagePromotion packagePromotion : overageList.keySet()) {
int daysSinceLastNotification =
packagePromotion
.getLastNotificationSent()
.map(sentDate -> Days.daysBetween(sentDate, clock.nowUtc()).getDays())
.orElse(Integer.MAX_VALUE);
if (daysSinceLastNotification < THIRTY_DAYS) {
// Don't send an email if notification was already sent within the last 30
// days
continue;
} else if (daysSinceLastNotification < FORTY_DAYS) {
// Send an upgrade email if last email was between 30 and 40 days ago
sendActiveDomainOverageEmail(
/* warning= */ false, packagePromotion, overageList.get(packagePromotion));
} else {
// Send a warning email
sendActiveDomainOverageEmail(
/* warning= */ true, packagePromotion, overageList.get(packagePromotion));
}
}
}
private void sendActiveDomainOverageEmail(
boolean warning, PackagePromotion packagePromotion, long activeDomains) {
String emailSubject =
warning ? packageDomainLimitWarningEmailSubject : packageDomainLimitUpgradeEmailSubject;
String emailTemplate =
warning ? packageDomainLimitWarningEmailBody : packageDomainLimitUpgradeEmailBody;
AllocationToken packageToken = tm().loadByKey(packagePromotion.getToken());
Optional<Registrar> registrar =
Registrar.loadByRegistrarIdCached(
Iterables.getOnlyElement(packageToken.getAllowedRegistrarIds()));
if (registrar.isPresent()) {
String body =
String.format(
emailTemplate,
packagePromotion.getId(),
packageToken.getToken(),
registrar.get().getRegistrarName(),
packagePromotion.getMaxDomains(),
activeDomains);
sendNotification(packageToken, emailSubject, body, registrar.get());
tm().put(packagePromotion.asBuilder().setLastNotificationSent(clock.nowUtc()).build());
} else {
throw new IllegalStateException(
String.format("Could not find registrar for package token %s", packageToken));
}
}
private void sendNotification(
AllocationToken packageToken, String subject, String body, Registrar registrar) {
logger.atInfo().log(
String.format(
"Compliance email sent to support regarding the %s registrar and the package with token"
+ " %s.",
registrar.getRegistrarName(), packageToken.getToken()));
sendEmailUtils.sendEmail(subject, body, ImmutableList.of(registrySupportEmail));
}
}

View File

@@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GmailClient;
import google.registry.model.domain.Domain;
import google.registry.model.domain.RegistryLock;
import google.registry.model.eppcommon.StatusValue;
@@ -40,7 +41,6 @@ import google.registry.request.auth.Auth;
import google.registry.tools.DomainLockUtils;
import google.registry.util.DateTimeUtils;
import google.registry.util.EmailMessage;
import google.registry.util.SendEmailService;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.AddressException;
@@ -84,7 +84,7 @@ public class RelockDomainAction implements Runnable {
private final InternetAddress alertRecipientAddress;
private final InternetAddress gSuiteOutgoingEmailAddress;
private final String supportEmail;
private final SendEmailService sendEmailService;
private final GmailClient gmailClient;
private final DomainLockUtils domainLockUtils;
private final Response response;
@@ -92,10 +92,10 @@ public class RelockDomainAction implements Runnable {
public RelockDomainAction(
@Parameter(OLD_UNLOCK_REVISION_ID_PARAM) long oldUnlockRevisionId,
@Parameter(PREVIOUS_ATTEMPTS_PARAM) int previousAttempts,
@Config("alertRecipientEmailAddress") InternetAddress alertRecipientAddress,
@Config("newAlertRecipientEmailAddress") InternetAddress alertRecipientAddress,
@Config("gSuiteOutgoingEmailAddress") InternetAddress gSuiteOutgoingEmailAddress,
@Config("supportEmail") String supportEmail,
SendEmailService sendEmailService,
GmailClient gmailClient,
DomainLockUtils domainLockUtils,
Response response) {
this.oldUnlockRevisionId = oldUnlockRevisionId;
@@ -103,7 +103,7 @@ public class RelockDomainAction implements Runnable {
this.alertRecipientAddress = alertRecipientAddress;
this.gSuiteOutgoingEmailAddress = gSuiteOutgoingEmailAddress;
this.supportEmail = supportEmail;
this.sendEmailService = sendEmailService;
this.gmailClient = gmailClient;
this.domainLockUtils = domainLockUtils;
this.response = response;
}
@@ -215,7 +215,7 @@ public class RelockDomainAction implements Runnable {
oldLock.getDomainName(),
t.getMessage(),
supportEmail);
sendEmailService.sendEmail(
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setFrom(gSuiteOutgoingEmailAddress)
.setBody(body)
@@ -245,7 +245,7 @@ public class RelockDomainAction implements Runnable {
String body =
String.format(RELOCK_SUCCESS_EMAIL_TEMPLATE, oldLock.getDomainName(), supportEmail);
sendEmailService.sendEmail(
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setFrom(gSuiteOutgoingEmailAddress)
.setBody(body)
@@ -264,7 +264,7 @@ public class RelockDomainAction implements Runnable {
.addAll(getEmailRecipients(oldLock.getRegistrarId()))
.add(alertRecipientAddress)
.build();
sendEmailService.sendEmail(
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setFrom(gSuiteOutgoingEmailAddress)
.setBody(body)
@@ -274,7 +274,7 @@ public class RelockDomainAction implements Runnable {
}
private void sendUnknownRevisionIdAlertEmail() {
sendEmailService.sendEmail(
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setFrom(gSuiteOutgoingEmailAddress)
.setBody(String.format(RELOCK_UNKNOWN_ID_FAILURE_EMAIL_TEMPLATE, oldUnlockRevisionId))

View File

@@ -274,10 +274,7 @@ public class RdeIO {
PendingDeposit key = input.getKey();
Tld tld = Tld.get(key.tld());
Optional<Cursor> cursor =
tm().transact(
() ->
tm().loadByKeyIfPresent(
Cursor.createScopedVKey(key.cursor(), tld)));
tm().loadByKeyIfPresent(Cursor.createScopedVKey(key.cursor(), tld));
DateTime position = getCursorTimeOrStartOfTime(cursor);
checkState(key.interval() != null, "Interval must be present");
DateTime newPosition = key.watermark().plus(key.interval());

View File

@@ -1371,41 +1371,45 @@ public final class RegistryConfig {
}
@Provides
@Config("packageCreateLimitEmailSubject")
public static String providePackageCreateLimitEmailSubject(RegistryConfigSettings config) {
return config.packageMonitoring.packageCreateLimitEmailSubject;
}
@Provides
@Config("packageCreateLimitEmailBody")
public static String providePackageCreateLimitEmailBody(RegistryConfigSettings config) {
return config.packageMonitoring.packageCreateLimitEmailBody;
}
@Provides
@Config("packageDomainLimitWarningEmailSubject")
public static String providePackageDomainLimitWarningEmailSubject(
@Config("bulkPricingPackageCreateLimitEmailSubject")
public static String provideBulkPricingPackageCreateLimitEmailSubject(
RegistryConfigSettings config) {
return config.packageMonitoring.packageDomainLimitWarningEmailSubject;
return config.bulkPricingPackageMonitoring.bulkPricingPackageCreateLimitEmailSubject;
}
@Provides
@Config("packageDomainLimitWarningEmailBody")
public static String providePackageDomainLimitWarningEmailBody(RegistryConfigSettings config) {
return config.packageMonitoring.packageDomainLimitWarningEmailBody;
}
@Provides
@Config("packageDomainLimitUpgradeEmailSubject")
public static String providePackageDomainLimitUpgradeEmailSubject(
@Config("bulkPricingPackageCreateLimitEmailBody")
public static String provideBulkPricingPackageCreateLimitEmailBody(
RegistryConfigSettings config) {
return config.packageMonitoring.packageDomainLimitUpgradeEmailSubject;
return config.bulkPricingPackageMonitoring.bulkPricingPackageCreateLimitEmailBody;
}
@Provides
@Config("packageDomainLimitUpgradeEmailBody")
public static String providePackageDomainLimitUpgradeEmailBody(RegistryConfigSettings config) {
return config.packageMonitoring.packageDomainLimitUpgradeEmailBody;
@Config("bulkPricingPackageDomainLimitWarningEmailSubject")
public static String provideBulkPricingPackageDomainLimitWarningEmailSubject(
RegistryConfigSettings config) {
return config.bulkPricingPackageMonitoring.bulkPricingPackageDomainLimitWarningEmailSubject;
}
@Provides
@Config("bulkPricingPackageDomainLimitWarningEmailBody")
public static String provideBulkPricingPackageDomainLimitWarningEmailBody(
RegistryConfigSettings config) {
return config.bulkPricingPackageMonitoring.bulkPricingPackageDomainLimitWarningEmailBody;
}
@Provides
@Config("bulkPricingPackageDomainLimitUpgradeEmailSubject")
public static String provideBulkPricingPackageDomainLimitUpgradeEmailSubject(
RegistryConfigSettings config) {
return config.bulkPricingPackageMonitoring.bulkPricingPackageDomainLimitUpgradeEmailSubject;
}
@Provides
@Config("bulkPricingPackageDomainLimitUpgradeEmailBody")
public static String provideBulkPricingPackageDomainLimitUpgradeEmailBody(
RegistryConfigSettings config) {
return config.bulkPricingPackageMonitoring.bulkPricingPackageDomainLimitUpgradeEmailBody;
}
private static String formatComments(String text) {
@@ -1547,6 +1551,11 @@ public final class RegistryConfig {
return CONFIG_SETTINGS.get().hibernate.connectionIsolation;
}
/** Returns true if per-transaction isolation level is enabled. */
public static boolean getHibernatePerTransactionIsolationEnabled() {
return CONFIG_SETTINGS.get().hibernate.perTransactionIsolation;
}
/** Returns true if hibernate.show_sql is enabled. */
public static String getHibernateLogSqlQueries() {
return CONFIG_SETTINGS.get().hibernate.logSqlQueries;

View File

@@ -42,7 +42,7 @@ public class RegistryConfigSettings {
public SslCertificateValidation sslCertificateValidation;
public ContactHistory contactHistory;
public DnsUpdate dnsUpdate;
public PackageMonitoring packageMonitoring;
public BulkPricingPackageMonitoring bulkPricingPackageMonitoring;
/** Configuration options that apply to the entire GCP project. */
public static class GcpProject {
@@ -115,6 +115,7 @@ public class RegistryConfigSettings {
/** Configuration for Hibernate. */
public static class Hibernate {
public boolean perTransactionIsolation;
public String connectionIsolation;
public String logSqlQueries;
public String hikariConnectionTimeout;
@@ -252,13 +253,13 @@ public class RegistryConfigSettings {
public String registryCcEmail;
}
/** Configuration for package compliance monitoring. */
public static class PackageMonitoring {
public String packageCreateLimitEmailSubject;
public String packageCreateLimitEmailBody;
public String packageDomainLimitWarningEmailSubject;
public String packageDomainLimitWarningEmailBody;
public String packageDomainLimitUpgradeEmailSubject;
public String packageDomainLimitUpgradeEmailBody;
/** Configuration for bulk pricing package compliance monitoring. */
public static class BulkPricingPackageMonitoring {
public String bulkPricingPackageCreateLimitEmailSubject;
public String bulkPricingPackageCreateLimitEmailBody;
public String bulkPricingPackageDomainLimitWarningEmailSubject;
public String bulkPricingPackageDomainLimitWarningEmailBody;
public String bulkPricingPackageDomainLimitUpgradeEmailSubject;
public String bulkPricingPackageDomainLimitUpgradeEmailBody;
}
}

View File

@@ -189,6 +189,12 @@ registryPolicy:
sunriseDomainCreateDiscount: 0.15
hibernate:
# Make it possible to specify the isolation level for each transaction. If set
# to true, nested transactions will throw an exception. If set to false, a
# transaction with the isolation override specified will still execute at the
# default level (specified below).
perTransactionIsolation: false
# Make 'SERIALIZABLE' the default isolation level to ensure correctness.
#
# Entities that are never involved in multi-table transactions may use optimistic
@@ -479,7 +485,7 @@ keyring:
# Configuration options relevant to the "nomulus" registry tool.
registryTool:
# OAuth client Id used by the tool.
# OAuth client ID used by the tool.
clientId: YOUR_CLIENT_ID
# OAuth client secret used by the tool.
clientSecret: YOUR_CLIENT_SECRET
@@ -553,55 +559,55 @@ sslCertificateValidation:
- secp256r1
- secp384r1
# Configuration options for the package compliance monitoring
packageMonitoring:
# Email subject text to notify tech support that a package has exceeded the limit for domain creates
packageCreateLimitEmailSubject: "ACTION REQUIRED: Package needs to be upgraded"
# Email body text template notify support that a package has exceeded the limit for domain creates
packageCreateLimitEmailBody: >
# Configuration options for the bulk pricing package compliance monitoring
bulkPricingPackageMonitoring:
# Email subject text to notify tech support that a bulk pricing package has exceeded the limit for domain creates
bulkPricingPackageCreateLimitEmailSubject: "ACTION REQUIRED: Bulk pricing package needs to be upgraded"
# Email body text template notify support that a bulk pricing package has exceeded the limit for domain creates
bulkPricingPackageCreateLimitEmailBody: >
Dear Support,
A package has exceeded its max create limit and needs to be upgraded to the
A bulk pricing package has exceeded its max create limit and needs to be upgraded to the
next tier.
Package ID: %1$s
Package Token: %2$s
Bulk Pricing ID: %1$s
Bulk Token: %2$s
Registrar: %3$s
Current Max Create Limit: %4$s
Creates Completed: %5$s
# Email subject text to notify support that a package has exceeded the limit
# Email subject text to notify support that a bulk pricing package has exceeded the limit
# for current active domains and a warning needs to be sent
packageDomainLimitWarningEmailSubject: "ACTION REQUIRED: Package has exceeded the domain limit - send warning"
# Email body text template to inform support that a package has exceeded the
# limit for active domains and a warning needs to be sent that the package
bulkPricingPackageDomainLimitWarningEmailSubject: "ACTION REQUIRED: Bulk pricing package has exceeded the domain limit - send warning"
# Email body text template to inform support that a bulk pricing package has exceeded the
# limit for active domains and a warning needs to be sent that the bulk pricing package
# will be upgraded in 30 days
packageDomainLimitWarningEmailBody: >
bulkPricingPackageDomainLimitWarningEmailBody: >
Dear Support,
A package has exceeded its max domain limit. Please send a warning to the
registrar that their package will be upgraded to the next tier in 30 days if
A bulk pricing package has exceeded its max domain limit. Please send a warning to the
registrar that their bulk pricing package will be upgraded to the next tier in 30 days if
the number of active domains does not return below the limit.
Package ID: %1$s
Package Token: %2$s
Bulk Pricing ID: %1$s
Bulk Token: %2$s
Registrar: %3$s
Active Domain Limit: %4$s
Current Active Domains: %5$s
# Email subject text to notify support that a package has exceeded the limit
# Email subject text to notify support that a bulk pricing package has exceeded the limit
# for current active domains for more than 30 days and needs to be upgraded
packageDomainLimitUpgradeEmailSubject: "ACTION REQUIRED: Package has exceeded the domain limit - upgrade package"
# Email body text template to inform support that a package has exceeded the
bulkPricingPackageDomainLimitUpgradeEmailSubject: "ACTION REQUIRED: Bulk pricing package has exceeded the domain limit - upgrade package"
# Email body text template to inform support that a bulk pricing package has exceeded the
# limit for active domains for more than 30 days and needs to be upgraded
packageDomainLimitUpgradeEmailBody: >
bulkPricingPackageDomainLimitUpgradeEmailBody: >
Dear Support,
A package has exceeded its max domain limit for over 30 days and needs to be
A bulk pricing package has exceeded its max domain limit for over 30 days and needs to be
upgraded to the next tier.
Package ID: %1$s
Package Token: %2$s
Bulk Pricing ID: %1$s
Bulk Token: %2$s
Registrar: %3$s
Active Domain Limit: %4$s
Current Active Domains: %5$s

View File

@@ -26,3 +26,6 @@ gSuite:
misc:
# We would rather have failures than timeouts, so reduce the number of retries
transientFailureRetries: 3
hibernate:
perTransactionIsolation: true

View File

@@ -35,6 +35,8 @@ import google.registry.model.eppoutput.EppResponse;
import google.registry.model.eppoutput.Result;
import google.registry.model.host.HostHistory;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.IsolationLevel;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import java.lang.annotation.Documented;
import java.util.Optional;
import javax.inject.Qualifier;
@@ -135,6 +137,14 @@ public class FlowModule {
return TransactionalFlow.class.isAssignableFrom(flowClass);
}
@Provides
@FlowScope
Optional<TransactionIsolationLevel> provideIsolationLevelOverride(
Class<? extends Flow> flowClass) {
return Optional.ofNullable(flowClass.getAnnotation(IsolationLevel.class))
.map(IsolationLevel::value);
}
@Provides
@FlowScope
@Superuser
@@ -166,7 +176,7 @@ public class FlowModule {
@FlowScope
@RegistrarId
static String provideRegistrarId(SessionMetadata sessionMetadata) {
// Treat a missing registrarId as null so we can always inject a non-null value. All we do with
// Treat a missing registrarId as null, so we can always inject a non-null value. All we do with
// the registrarId is log it (as "") or detect its absence, both of which work fine with empty.
return Strings.nullToEmpty(sessionMetadata.getRegistrarId());
}

View File

@@ -28,6 +28,8 @@ import google.registry.flows.session.LoginFlow;
import google.registry.model.eppcommon.Trid;
import google.registry.model.eppoutput.EppOutput;
import google.registry.monitoring.whitebox.EppMetric;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -42,6 +44,7 @@ public class FlowRunner {
@Inject TransportCredentials credentials;
@Inject EppRequestSource eppRequestSource;
@Inject Provider<Flow> flowProvider;
@Inject Optional<TransactionIsolationLevel> isolationLevelOverride;
@Inject Class<? extends Flow> flowClass;
@Inject @InputXml byte[] inputXmlBytes;
@Inject @DryRun boolean isDryRun;
@@ -91,7 +94,8 @@ public class FlowRunner {
} catch (EppException e) {
throw new EppRuntimeException(e);
}
});
},
isolationLevelOverride.orElse(null));
} catch (DryRunException e) {
return e.output;
} catch (EppRuntimeException e) {

View File

@@ -152,7 +152,7 @@ import org.joda.time.Duration;
* @error {@link DomainCreateFlow.MustHaveSignedMarksInCurrentPhaseException}
* @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException}
* @error {@link DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException}
* @error {@link DomainCreateFlow.PackageDomainRegisteredForTooManyYearsException}
* @error {@link BulkDomainRegisteredForTooManyYearsException}
* @error {@link DomainCreateFlow.SignedMarksOnlyDuringSunriseException}
* @error {@link DomainFlowTmchUtils.NoMarksFoundMatchingDomainException}
* @error {@link DomainFlowTmchUtils.FoundMarkNotYetValidException}
@@ -405,12 +405,11 @@ public final class DomainCreateFlow implements TransactionalFlow {
: hasClaimsNotice ? LordnPhase.CLAIMS : LordnPhase.NONE);
Domain domain = domainBuilder.build();
if (allocationToken.isPresent()
&& allocationToken.get().getTokenType().equals(TokenType.PACKAGE)) {
&& allocationToken.get().getTokenType().equals(TokenType.BULK_PRICING)) {
if (years > 1) {
throw new PackageDomainRegisteredForTooManyYearsException(allocationToken.get().getToken());
throw new BulkDomainRegisteredForTooManyYearsException(allocationToken.get().getToken());
}
domain =
domain.asBuilder().setCurrentPackageToken(allocationToken.get().createVKey()).build();
domain = domain.asBuilder().setCurrentBulkToken(allocationToken.get().createVKey()).build();
}
DomainHistory domainHistory =
buildDomainHistory(domain, tld, now, period, tld.getAddGracePeriodLength());
@@ -763,13 +762,12 @@ public final class DomainCreateFlow implements TransactionalFlow {
}
}
/** Package domain registered for too many years. */
static class PackageDomainRegisteredForTooManyYearsException extends CommandUseErrorException {
public PackageDomainRegisteredForTooManyYearsException(String token) {
/** Bulk pricing domain registered for too many years. */
static class BulkDomainRegisteredForTooManyYearsException extends CommandUseErrorException {
public BulkDomainRegisteredForTooManyYearsException(String token) {
super(
String.format(
"The package token %s cannot be used to register names for longer than 1 year.",
token));
"The bulk token %s cannot be used to register names for longer than 1 year.", token));
}
}
}

View File

@@ -155,13 +155,13 @@ public final class DomainInfoFlow implements Flow {
if (!gracePeriodStatuses.isEmpty()) {
extensions.add(RgpInfoExtension.create(gracePeriodStatuses));
}
Optional<BulkTokenExtension> packageInfo =
Optional<BulkTokenExtension> bulkPricingInfo =
eppInput.getSingleExtension(BulkTokenExtension.class);
if (packageInfo.isPresent()) {
// Package info was requested.
if (bulkPricingInfo.isPresent()) {
// Bulk pricing info was requested.
if (isSuperuser || registrarId.equals(domain.getCurrentSponsorRegistrarId())) {
// Only show package info to owning registrar or superusers
extensions.add(BulkTokenResponseExtension.create(domain.getCurrentPackageToken()));
// Only show bulk pricing info to owning registrar or superusers
extensions.add(BulkTokenResponseExtension.create(domain.getCurrentBulkToken()));
}
}
Optional<FeeInfoCommandExtensionV06> feeInfo =

View File

@@ -30,7 +30,7 @@ import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
import static google.registry.flows.domain.DomainFlowUtils.validateRegistrationPeriod;
import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive;
import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears;
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeApplyPackageRemovalToken;
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeApplyBulkPricingRemovalToken;
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyTokenAllowedOnDomain;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RENEW;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -53,8 +53,8 @@ import google.registry.flows.custom.DomainRenewFlowCustomLogic.BeforeResponseRet
import google.registry.flows.custom.DomainRenewFlowCustomLogic.BeforeSaveParameters;
import google.registry.flows.custom.EntityChanges;
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemoveDomainTokenOnPackageDomainException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.RemoveDomainTokenOnNonPackageDomainException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemoveDomainTokenOnBulkPricingDomainException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.RemoveDomainTokenOnNonBulkPricingDomainException;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingEvent;
@@ -121,8 +121,8 @@ import org.joda.time.Duration;
* @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException}
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
* @error {@link DomainRenewFlow.IncorrectCurrentExpirationDateException}
* @error {@link MissingRemoveDomainTokenOnPackageDomainException}
* @error {@link RemoveDomainTokenOnNonPackageDomainException}
* @error {@link MissingRemoveDomainTokenOnBulkPricingDomainException}
* @error {@link RemoveDomainTokenOnNonBulkPricingDomainException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException}
* @error {@link
@@ -194,7 +194,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
verifyRenewAllowed(authInfo, existingDomain, command, allocationToken);
// If client passed an applicable static token this updates the domain
existingDomain = maybeApplyPackageRemovalToken(existingDomain, allocationToken);
existingDomain = maybeApplyBulkPricingRemovalToken(existingDomain, allocationToken);
int years = command.getPeriod().getValue();
DateTime newExpirationTime =
@@ -328,7 +328,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
checkHasBillingAccount(registrarId, existingDomain.getTld());
}
verifyUnitIsYears(command.getPeriod());
// We only allow __REMOVE_PACKAGE__ token on promo package domains for now
// We only allow __REMOVE_PACKAGE__ token on bulk pricing domains for now
verifyTokenAllowedOnDomain(existingDomain, allocationToken);
// If the date they specify doesn't match the expiration, fail. (This is an idempotence check).
if (!command.getCurrentExpirationDate().equals(

View File

@@ -154,9 +154,9 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
tm().loadByKey(existingDomain.getAutorenewBillingEvent());
HistoryEntryId domainHistoryId = createHistoryEntryId(existingDomain);
historyBuilder.setRevisionId(domainHistoryId.getRevisionId());
boolean hasPackageToken = existingDomain.getCurrentPackageToken().isPresent();
boolean hasBulkToken = existingDomain.getCurrentBulkToken().isPresent();
Money renewalPrice =
hasPackageToken ? null : existingBillingRecurrence.getRenewalPrice().orElse(null);
hasBulkToken ? null : existingBillingRecurrence.getRenewalPrice().orElse(null);
Optional<BillingEvent> billingEvent =
transferData.getTransferPeriod().getValue() == 0
? Optional.empty()
@@ -172,10 +172,10 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
Tld.get(tldStr),
targetId,
transferData.getTransferRequestTime(),
// When removing a domain from a package it should return to the
// When removing a domain from bulk pricing it should return to the
// default recurrence billing behavior so the existing recurrence
// billing event should not be passed in.
hasPackageToken ? null : existingBillingRecurrence)
hasBulkToken ? null : existingBillingRecurrence)
.getRenewCost())
.setEventTime(now)
.setBillingTime(now.plus(Tld.get(tldStr).getTransferGracePeriodLength()))
@@ -213,7 +213,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.setRegistrarId(gainingRegistrarId)
.setEventTime(newExpirationTime)
.setRenewalPriceBehavior(
hasPackageToken
hasBulkToken
? RenewalPriceBehavior.DEFAULT
: existingBillingRecurrence.getRenewalPriceBehavior())
.setRenewalPrice(renewalPrice)
@@ -258,9 +258,9 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
.orElseGet(ImmutableSet::of))
.setLastEppUpdateTime(now)
.setLastEppUpdateRegistrarId(registrarId)
// Even if the existing domain had a package token, that package token should be removed
// Even if the existing domain had a bulk token, that bulk token should be removed
// on transfer
.setCurrentPackageToken(null)
.setCurrentBulkToken(null)
.build();
Tld tld = Tld.get(existingDomain.getTld());

View File

@@ -201,11 +201,12 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
Optional<FeesAndCredits> feesAndCredits;
if (period.getValue() == 0) {
feesAndCredits = Optional.empty();
} else if (!existingDomain.getCurrentPackageToken().isPresent()) {
} else if (!existingDomain.getCurrentBulkToken().isPresent()) {
feesAndCredits =
Optional.of(pricingLogic.getTransferPrice(tld, targetId, now, existingBillingRecurrence));
} else {
// If existing domain is in a package, calculate the transfer price with default renewal price
// If existing domain is in a bulk pricing package, calculate the transfer price with default
// renewal price
// behavior
feesAndCredits =
period.getValue() == 0

View File

@@ -146,7 +146,7 @@ public final class DomainTransferUtils {
return builder
.add(
createGainingClientAutorenewEvent(
existingDomain.getCurrentPackageToken().isPresent()
existingDomain.getCurrentBulkToken().isPresent()
? existingBillingRecurrence
.asBuilder()
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)

View File

@@ -242,19 +242,19 @@ public class AllocationTokenFlowUtils {
public static void verifyTokenAllowedOnDomain(
Domain domain, Optional<AllocationToken> allocationToken) throws EppException {
boolean domainHasPackageToken = domain.getCurrentPackageToken().isPresent();
boolean domainHasBulkToken = domain.getCurrentBulkToken().isPresent();
boolean hasRemoveDomainToken =
allocationToken.isPresent()
&& TokenBehavior.REMOVE_DOMAIN.equals(allocationToken.get().getTokenBehavior());
if (hasRemoveDomainToken && !domainHasPackageToken) {
throw new RemoveDomainTokenOnNonPackageDomainException();
} else if (!hasRemoveDomainToken && domainHasPackageToken) {
throw new MissingRemoveDomainTokenOnPackageDomainException();
if (hasRemoveDomainToken && !domainHasBulkToken) {
throw new RemoveDomainTokenOnNonBulkPricingDomainException();
} else if (!hasRemoveDomainToken && domainHasBulkToken) {
throw new MissingRemoveDomainTokenOnBulkPricingDomainException();
}
}
public static Domain maybeApplyPackageRemovalToken(
public static Domain maybeApplyBulkPricingRemovalToken(
Domain domain, Optional<AllocationToken> allocationToken) {
if (!allocationToken.isPresent()
|| !TokenBehavior.REMOVE_DOMAIN.equals(allocationToken.get().getTokenBehavior())) {
@@ -274,10 +274,10 @@ public class AllocationTokenFlowUtils {
tm().getEntityManager().flush();
tm().getEntityManager().clear();
// Remove current package token
// Remove current bulk token
return domain
.asBuilder()
.setCurrentPackageToken(null)
.setCurrentBulkToken(null)
.setAutorenewBillingEvent(newBillingRecurrence.createVKey())
.build();
}
@@ -338,19 +338,19 @@ public class AllocationTokenFlowUtils {
}
}
/** The __REMOVEDOMAIN__ token is missing on a package domain command */
public static class MissingRemoveDomainTokenOnPackageDomainException
/** The __REMOVEDOMAIN__ token is missing on a bulk pricing domain command */
public static class MissingRemoveDomainTokenOnBulkPricingDomainException
extends AssociationProhibitsOperationException {
MissingRemoveDomainTokenOnPackageDomainException() {
super("Domains that are inside packages cannot be explicitly renewed or transferred");
MissingRemoveDomainTokenOnBulkPricingDomainException() {
super("Domains that are inside bulk pricing cannot be explicitly renewed or transferred");
}
}
/** The __REMOVEDOMAIN__ token is not allowed on non package domains */
public static class RemoveDomainTokenOnNonPackageDomainException
/** The __REMOVEDOMAIN__ token is not allowed on non bulk pricing domains */
public static class RemoveDomainTokenOnNonBulkPricingDomainException
extends AssociationProhibitsOperationException {
RemoveDomainTokenOnNonPackageDomainException() {
super("__REMOVEDOMAIN__ token is not allowed on non package domains");
RemoveDomainTokenOnNonBulkPricingDomainException() {
super("__REMOVEDOMAIN__ token is not allowed on non bulk pricing domains");
}
}
}

View File

@@ -200,7 +200,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
.setStatusValues(domainBase.getStatusValues())
.setTransferData(domainBase.getTransferData())
.setLordnPhase(domainBase.getLordnPhase())
.setCurrentPackageToken(domainBase.getCurrentPackageToken().orElse(null));
.setCurrentBulkToken(domainBase.getCurrentBulkToken().orElse(null));
}
}
}

View File

@@ -267,8 +267,12 @@ public class DomainBase extends EppResource
@Enumerated(EnumType.STRING)
LordnPhase lordnPhase = LordnPhase.NONE;
/** The {@link AllocationToken} for the package this domain is currently a part of. */
@Nullable VKey<AllocationToken> currentPackageToken;
/**
* The {@link AllocationToken} for the bulk pricing package this domain is currently a part of.
*/
@Nullable
@Column(name = "current_package_token")
VKey<AllocationToken> currentBulkToken;
public LordnPhase getLordnPhase() {
return lordnPhase;
@@ -302,8 +306,8 @@ public class DomainBase extends EppResource
return smdId;
}
public Optional<VKey<AllocationToken>> getCurrentPackageToken() {
return Optional.ofNullable(currentPackageToken);
public Optional<VKey<AllocationToken>> getCurrentBulkToken() {
return Optional.ofNullable(currentBulkToken);
}
/**
@@ -478,7 +482,7 @@ public class DomainBase extends EppResource
// events.
.setAutorenewBillingEvent(transferData.getServerApproveAutorenewEvent())
.setAutorenewPollMessage(transferData.getServerApproveAutorenewPollMessage())
.setCurrentPackageToken(null);
.setCurrentBulkToken(null);
if (transferData.getTransferPeriod().getValue() == 1) {
// Set the grace period using a key to the pre-scheduled transfer billing event. Not using
// GracePeriod.forBillingEvent() here in order to avoid the actual fetch.
@@ -910,23 +914,22 @@ public class DomainBase extends EppResource
return thisCastToDerived();
}
public B setCurrentPackageToken(@Nullable VKey<AllocationToken> currentPackageToken) {
if (currentPackageToken == null) {
getInstance().currentPackageToken = currentPackageToken;
public B setCurrentBulkToken(@Nullable VKey<AllocationToken> currentBulkToken) {
if (currentBulkToken == null) {
getInstance().currentBulkToken = currentBulkToken;
return thisCastToDerived();
}
AllocationToken token =
tm().transact(() -> tm().loadByKeyIfPresent(currentPackageToken))
tm().transact(() -> tm().loadByKeyIfPresent(currentBulkToken))
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"The package token %s does not exist",
currentPackageToken.getKey())));
"The bulk token %s does not exist", currentBulkToken.getKey())));
checkArgument(
token.getTokenType().equals(TokenType.PACKAGE),
"The currentPackageToken must have a PACKAGE TokenType");
getInstance().currentPackageToken = currentPackageToken;
token.getTokenType().equals(TokenType.BULK_PRICING),
"The currentBulkToken must have a BULK_PRICING TokenType");
getInstance().currentBulkToken = currentBulkToken;
return thisCastToDerived();
}
}

View File

@@ -119,9 +119,14 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
/** Type of the token that indicates how and where it should be used. */
public enum TokenType {
/** Token used for bulk pricing */
BULK_PRICING,
/** Token saved on a TLD to use if no other token is passed from the client */
DEFAULT_PROMO,
/** Token used for package pricing */
/** This is the old name for what is now BULK_PRICING. */
// TODO(sarahbot@): Remove this type once all tokens of this type have been scrubbed from the
// database
@Deprecated
PACKAGE,
/** Invalid after use */
SINGLE_USE,
@@ -137,8 +142,8 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
/** No special behavior */
DEFAULT,
/**
* REMOVE_DOMAIN triggers domain removal from promotional bulk (package) pricing, bypasses
* DEFAULT token validations.
* REMOVE_DOMAIN triggers domain removal from a bulk pricing package, bypasses DEFAULT token
* validations.
*/
REMOVE_DOMAIN
}
@@ -344,12 +349,13 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
checkArgumentNotNull(getInstance().tokenType, "Token type must be specified");
checkArgument(!Strings.isNullOrEmpty(getInstance().token), "Token must not be null or empty");
checkArgument(
!getInstance().tokenType.equals(TokenType.PACKAGE)
!getInstance().tokenType.equals(TokenType.BULK_PRICING)
|| getInstance().renewalPriceBehavior.equals(RenewalPriceBehavior.SPECIFIED),
"Package tokens must have renewalPriceBehavior set to SPECIFIED");
"Bulk tokens must have renewalPriceBehavior set to SPECIFIED");
checkArgument(
!getInstance().tokenType.equals(TokenType.PACKAGE) || !getInstance().discountPremiums,
"Package tokens cannot discount premium names");
!getInstance().tokenType.equals(TokenType.BULK_PRICING)
|| !getInstance().discountPremiums,
"Bulk tokens cannot discount premium names");
checkArgument(
getInstance().domainName == null || TokenType.SINGLE_USE.equals(getInstance().tokenType),
"Domain name can only be specified for SINGLE_USE tokens");
@@ -358,10 +364,10 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
|| TokenType.SINGLE_USE.equals(getInstance().tokenType),
"Redemption history entry can only be specified for SINGLE_USE tokens");
checkArgument(
getInstance().tokenType != TokenType.PACKAGE
getInstance().tokenType != TokenType.BULK_PRICING
|| (getInstance().allowedClientIds != null
&& getInstance().allowedClientIds.size() == 1),
"PACKAGE tokens must have exactly one allowed client registrar");
"BULK_PRICING tokens must have exactly one allowed client registrar");
checkArgument(
getInstance().discountFraction > 0 || !getInstance().discountPremiums,
"Discount premiums can only be specified along with a discount fraction");

View File

@@ -36,46 +36,53 @@ import org.hibernate.annotations.Type;
import org.joda.money.Money;
import org.joda.time.DateTime;
/** An entity representing a package promotion. */
@Entity
/**
* An entity representing a bulk pricing promotion. Note that this table is still called
* PackagePromotion in Cloud SQL.
*/
@Entity(name = "PackagePromotion")
@javax.persistence.Table(indexes = {@javax.persistence.Index(columnList = "token")})
public class PackagePromotion extends ImmutableObject implements Buildable {
public class BulkPricingPackage extends ImmutableObject implements Buildable {
/** An autogenerated identifier for the package promotion. */
/** An autogenerated identifier for the bulk pricing promotion. */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long packagePromotionId;
@Column(name = "package_promotion_id")
long bulkPricingId;
/** The allocation token string for the package. */
/** The allocation token string for the bulk pricing package. */
@Column(nullable = false)
VKey<AllocationToken> token;
/** The maximum number of active domains the package allows at any given time. */
/** The maximum number of active domains the bulk pricing package allows at any given time. */
@Column(nullable = false)
int maxDomains;
/** The maximum number of domains that can be created in the package each year. */
/** The maximum number of domains that can be created in the bulk pricing package each year. */
@Column(nullable = false)
int maxCreates;
/** The annual price of the package. */
/** The annual price of the bulk pricing package. */
@Type(type = JodaMoneyType.TYPE_NAME)
@Columns(
columns = {
@Column(name = "package_price_amount", nullable = false),
@Column(name = "package_price_currency", nullable = false)
})
Money packagePrice;
Money bulkPrice;
/** The next billing date of the package. */
/** The next billing date of the bulk pricing package. */
@Column(nullable = false)
DateTime nextBillingDate = END_OF_TIME;
/** Date the last warning email was sent that the package has exceeded the maxDomains limit. */
/**
* Date the last warning email was sent that the bulk pricing package has exceeded the maxDomains
* limit.
*/
@Nullable DateTime lastNotificationSent;
public long getId() {
return packagePromotionId;
return bulkPricingId;
}
public VKey<AllocationToken> getToken() {
@@ -90,8 +97,8 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
return maxCreates;
}
public Money getPackagePrice() {
return packagePrice;
public Money getBulkPrice() {
return bulkPrice;
}
public DateTime getNextBillingDate() {
@@ -102,18 +109,18 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
return Optional.ofNullable(lastNotificationSent);
}
/** Loads and returns a PackagePromotion entity by its token string directly from Cloud SQL. */
public static Optional<PackagePromotion> loadByTokenString(String tokenString) {
/** Loads and returns a BulkPricingPackage entity by its token string directly from Cloud SQL. */
public static Optional<BulkPricingPackage> loadByTokenString(String tokenString) {
tm().assertInTransaction();
return tm().query("FROM PackagePromotion WHERE token = :token", PackagePromotion.class)
return tm().query("FROM PackagePromotion WHERE token = :token", BulkPricingPackage.class)
.setParameter("token", VKey.create(AllocationToken.class, tokenString))
.getResultStream()
.findFirst();
}
@Override
public VKey<PackagePromotion> createVKey() {
return VKey.create(PackagePromotion.class, packagePromotionId);
public VKey<BulkPricingPackage> createVKey() {
return VKey.create(BulkPricingPackage.class, bulkPricingId);
}
@Override
@@ -121,28 +128,29 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
return new Builder(clone(this));
}
/** A builder for constructing {@link PackagePromotion} objects, since they are immutable. */
public static class Builder extends Buildable.Builder<PackagePromotion> {
/** A builder for constructing {@link BulkPricingPackage} objects, since they are immutable. */
public static class Builder extends Buildable.Builder<BulkPricingPackage> {
public Builder() {}
private Builder(PackagePromotion instance) {
private Builder(BulkPricingPackage instance) {
super(instance);
}
@Override
public PackagePromotion build() {
public BulkPricingPackage build() {
checkArgumentNotNull(getInstance().token, "Allocation token must be specified");
AllocationToken allocationToken = tm().transact(() -> tm().loadByKey(getInstance().token));
checkArgument(
allocationToken.tokenType == TokenType.PACKAGE,
"Allocation token must be a PACKAGE type");
allocationToken.tokenType == TokenType.BULK_PRICING,
"Allocation token must be a BULK_PRICING type");
return super.build();
}
public Builder setToken(AllocationToken token) {
checkArgumentNotNull(token, "Allocation token must not be null");
checkArgument(
token.tokenType == TokenType.PACKAGE, "Allocation token must be a PACKAGE type");
token.tokenType == TokenType.BULK_PRICING,
"Allocation token must be a BULK_PRICING type");
getInstance().token = token.createVKey();
return this;
}
@@ -159,9 +167,9 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
return this;
}
public Builder setPackagePrice(Money packagePrice) {
checkArgumentNotNull(packagePrice, "Package price must not be null");
getInstance().packagePrice = packagePrice;
public Builder setBulkPrice(Money bulkPrice) {
checkArgumentNotNull(bulkPrice, "Bulk price must not be null");
getInstance().bulkPrice = bulkPrice;
return this;
}

View File

@@ -26,6 +26,7 @@ import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
import google.registry.model.UnsafeSerializable;
import google.registry.tools.GsonUtils.GsonPostProcessable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -55,7 +56,8 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlTransient
@Embeddable
@MappedSuperclass
public class Address extends ImmutableObject implements Jsonifiable, UnsafeSerializable {
public class Address extends ImmutableObject
implements Jsonifiable, UnsafeSerializable, GsonPostProcessable {
/**
* At most three lines of addresses parsed from XML elements.
@@ -152,6 +154,16 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
return new Builder<>(clone(this));
}
@Override
public void postProcess() {
if (street == null || street.isEmpty()) {
return;
}
streetLine1 = street.get(0);
streetLine2 = street.size() >= 2 ? street.get(1) : null;
streetLine3 = street.size() >= 3 ? street.get(2) : null;
}
/** A builder for constructing {@link Address}. */
public static class Builder<T extends Address> extends Buildable.Builder<T> {
@@ -228,11 +240,6 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
*/
@SuppressWarnings("unused")
void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
if (street == null || street.isEmpty()) {
return;
}
streetLine1 = street.get(0);
streetLine2 = street.size() >= 2 ? street.get(1) : null;
streetLine3 = street.size() >= 3 ? street.get(2) : null;
postProcess();
}
}

View File

@@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static com.google.common.collect.Sets.immutableEnumSet;
@@ -49,7 +48,6 @@ import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.gson.annotations.Expose;
import com.google.re2j.Pattern;
import google.registry.model.Buildable;
@@ -242,7 +240,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
@Expose Set<String> allowedTlds;
/** Host name of WHOIS server. */
String whoisServer;
@Expose String whoisServer;
/** Base URLs for the registrar's RDAP servers. */
Set<String> rdapBaseUrls;
@@ -326,10 +324,10 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
RegistrarAddress internationalizedAddress;
/** Voice number. */
String phoneNumber;
@Expose String phoneNumber;
/** Fax number. */
String faxNumber;
@Expose String faxNumber;
/** Email address. */
@Expose String emailAddress;
@@ -364,7 +362,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
@Expose @Nullable Map<CurrencyUnit, String> billingAccountMap;
/** URL of registrar's website. */
String url;
@Expose String url;
/**
* ICANN referral email address.
@@ -556,7 +554,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
* address.
*/
public ImmutableSortedSet<RegistrarPoc> getContacts() {
return Streams.stream(getContactsIterable())
return getContactPocs().stream()
.filter(Objects::nonNull)
.collect(toImmutableSortedSet(CONTACT_EMAIL_COMPARATOR));
}
@@ -566,7 +564,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
* their email address.
*/
public ImmutableSortedSet<RegistrarPoc> getContactsOfType(final RegistrarPoc.Type type) {
return Streams.stream(getContactsIterable())
return getContactPocs().stream()
.filter(Objects::nonNull)
.filter((@Nullable RegistrarPoc contact) -> contact.getTypes().contains(type))
.collect(toImmutableSortedSet(CONTACT_EMAIL_COMPARATOR));
@@ -580,13 +578,13 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
return getContacts().stream().filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse).findFirst();
}
private Iterable<RegistrarPoc> getContactsIterable() {
private ImmutableSet<RegistrarPoc> getContactPocs() {
return tm().transact(
() ->
tm().query("FROM RegistrarPoc WHERE registrarId = :registrarId", RegistrarPoc.class)
.setParameter("registrarId", registrarId)
.getResultStream()
.collect(toImmutableList()));
.collect(toImmutableSet()));
}
@Override
@@ -732,8 +730,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
.map(Tld::createVKey)
.collect(toImmutableSet());
Set<VKey<Tld>> missingTldKeys =
Sets.difference(
newTldKeys, tm().transact(() -> tm().loadByKeysIfPresent(newTldKeys)).keySet());
Sets.difference(newTldKeys, tm().loadByKeysIfPresent(newTldKeys).keySet());
checkArgument(missingTldKeys.isEmpty(), "Trying to set nonexistent TLDs: %s", missingTldKeys);
getInstance().allowedTlds = ImmutableSortedSet.copyOf(allowedTlds);
return this;

View File

@@ -23,6 +23,7 @@ import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.flows.ServerTridProviderModule;
import google.registry.flows.custom.CustomLogicFactoryModule;
import google.registry.groups.DirectoryModule;
import google.registry.groups.GmailModule;
import google.registry.groups.GroupsModule;
import google.registry.groups.GroupssettingsModule;
import google.registry.keyring.KeyringModule;
@@ -54,6 +55,7 @@ import javax.inject.Singleton;
DirectoryModule.class,
DummyKeyringModule.class,
FrontendRequestComponentModule.class,
GmailModule.class,
GroupsModule.class,
GroupssettingsModule.class,
GsonModule.class,

View File

@@ -29,6 +29,7 @@ import google.registry.ui.server.console.ConsoleDomainGetAction;
import google.registry.ui.server.console.RegistrarsAction;
import google.registry.ui.server.console.settings.ContactAction;
import google.registry.ui.server.console.settings.SecurityAction;
import google.registry.ui.server.console.settings.WhoisRegistrarFieldsAction;
import google.registry.ui.server.registrar.ConsoleOteSetupAction;
import google.registry.ui.server.registrar.ConsoleRegistrarCreatorAction;
import google.registry.ui.server.registrar.ConsoleUiAction;
@@ -73,6 +74,8 @@ interface FrontendRequestComponent {
SecurityAction securityAction();
WhoisRegistrarFieldsAction whoisRegistrarFieldsAction();
@Subcomponent.Builder
abstract class Builder implements RequestComponentBuilder<FrontendRequestComponent> {
@Override public abstract Builder requestModule(RequestModule requestModule);

View File

@@ -0,0 +1,33 @@
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.persistence;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates which {@link TransactionIsolationLevel} that a {@link
* google.registry.flows.TransactionalFlow} show run at.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IsolationLevel {
TransactionIsolationLevel value();
}

View File

@@ -27,9 +27,9 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Provides;
@@ -65,7 +65,6 @@ import org.hibernate.cfg.Environment;
/** Dagger module class for the persistence layer. */
@Module
public abstract class PersistenceModule {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
// This name must be the same as the one defined in persistence.xml.
public static final String PERSISTENCE_UNIT_NAME = "nomulus";
@@ -166,12 +165,10 @@ public abstract class PersistenceModule {
// Override the default minimum which is tuned for the Registry server. A worker VM should
// release all connections if it no longer interacts with the database.
overrides.put(HIKARI_MINIMUM_IDLE, "0");
/**
* Disable Hikari's maxPoolSize limit check by setting it to an absurdly large number. The
* effective (and desirable) limit is the number of pipeline threads on the pipeline worker,
* which can be configured using pipeline options. See {@link RegistryPipelineOptions} for more
* information.
*/
// Disable Hikari's maxPoolSize limit check by setting it to an absurdly large number. The
// effective (and desirable) limit is the number of pipeline threads on the pipeline worker,
// which can be configured using pipeline options. See {@link RegistryPipelineOptions} for more
// information.
overrides.put(HIKARI_MAXIMUM_POOL_SIZE, String.valueOf(Integer.MAX_VALUE));
instanceConnectionNameOverride
.map(Provider::get)
@@ -303,7 +300,7 @@ public abstract class PersistenceModule {
private static EntityManagerFactory create(Map<String, String> properties) {
// If there are no annotated classes, we can create the EntityManagerFactory from the generic
// method. Otherwise we have to use a more tailored approach. Note that this adds to the set
// method. Otherwise, we have to use a more tailored approach. Note that this adds to the set
// of annotated classes defined in the configuration, it does not override them.
EntityManagerFactory emf =
Persistence.createEntityManagerFactory(
@@ -322,8 +319,7 @@ public abstract class PersistenceModule {
overrides.put(Environment.USER, credential.login());
overrides.put(Environment.PASS, credential.password());
} catch (Throwable e) {
// TODO(b/184631990): after SQL becomes primary, throw an exception to fail fast
logger.atSevere().withCause(e).log("Failed to get SQL credential from Secret Manager.");
throw new RuntimeException("Failed to get SQL credential from Secret Manager.", e);
}
}
@@ -340,11 +336,13 @@ public abstract class PersistenceModule {
TRANSACTION_SERIALIZABLE;
private final int value;
private final String mode;
TransactionIsolationLevel() {
try {
// name() is final in parent class (Enum.java), therefore safe to call in constructor.
value = Connection.class.getField(name()).getInt(null);
mode = name().substring(12).replace('_', ' ');
} catch (Exception e) {
throw new IllegalStateException(
String.format(
@@ -356,6 +354,14 @@ public abstract class PersistenceModule {
public final int getValue() {
return value;
}
public final String getMode() {
return mode;
}
public static TransactionIsolationLevel fromMode(String mode) {
return valueOf(String.format("TRANSACTION_%s", Ascii.toUpperCase(mode.replace(' ', '_'))));
}
}
/** Types of {@link JpaTransactionManager JpaTransactionManagers}. */

View File

@@ -14,6 +14,7 @@
package google.registry.persistence.transaction;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.VKey;
import java.util.function.Supplier;
import javax.persistence.EntityManager;
@@ -64,9 +65,21 @@ public interface JpaTransactionManager extends TransactionManager {
/** Executes the work in a transaction with no retries and returns the result. */
<T> T transactNoRetry(Supplier<T> work);
/**
* Executes the work in a transaction at the given {@link TransactionIsolationLevel} with no
* retries and returns the result.
*/
<T> T transactNoRetry(Supplier<T> work, TransactionIsolationLevel isolationLevel);
/** Executes the work in a transaction with no retries. */
void transactNoRetry(Runnable work);
/**
* Executes the work in a transaction at the given {@link TransactionIsolationLevel} with no
* retries.
*/
void transactNoRetry(Runnable work, TransactionIsolationLevel isolationLevel);
/** Deletes the entity by its id, throws exception if the entity is not deleted. */
<T> void assertDelete(VKey<T> key);
@@ -84,4 +97,13 @@ public interface JpaTransactionManager extends TransactionManager {
static Query setQueryFetchSize(Query query, int fetchSize) {
return query.setHint("org.hibernate.fetchSize", fetchSize);
}
/** Return the default {@link TransactionIsolationLevel} specified via the config file. */
TransactionIsolationLevel getDefaultTransactionIsolationLevel();
/** Return the {@link TransactionIsolationLevel} used in the current transaction. */
TransactionIsolationLevel getCurrentTransactionIsolationLevel();
/** Asserts that the current transaction runs at the given level. */
void assertTransactionIsolationLevel(TransactionIsolationLevel expectedLevel);
}

View File

@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.config.RegistryConfig.getHibernatePerTransactionIsolationEnabled;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static java.util.AbstractMap.SimpleEntry;
import static java.util.stream.Collectors.joining;
@@ -31,6 +32,7 @@ import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import google.registry.model.ImmutableObject;
import google.registry.persistence.JpaRetries;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.VKey;
import google.registry.util.Clock;
import google.registry.util.Retrier;
@@ -65,6 +67,7 @@ import javax.persistence.TemporalType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.metamodel.EntityType;
import org.hibernate.cfg.Environment;
import org.joda.time.DateTime;
/** Implementation of {@link JpaTransactionManager} for JPA compatible database. */
@@ -77,9 +80,6 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
private final EntityManagerFactory emf;
private final Clock clock;
// TODO(b/177588434): Investigate alternatives for managing transaction information. ThreadLocal
// adds an unnecessary restriction that each request has to be processed by one thread
// synchronously.
private static final ThreadLocal<TransactionInfo> transactionInfo =
ThreadLocal.withInitial(TransactionInfo::new);
@@ -137,17 +137,46 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
}
@Override
public <T> T transact(Supplier<T> work) {
// This prevents inner transaction from retrying, thus avoiding a cascade retry effect.
if (inTransaction()) {
return transactNoRetry(work);
public void assertTransactionIsolationLevel(TransactionIsolationLevel expectedLevel) {
assertInTransaction();
TransactionIsolationLevel currentLevel = getCurrentTransactionIsolationLevel();
if (currentLevel != expectedLevel) {
throw new IllegalStateException(
String.format(
"Current transaction isolation level (%s) is not as expected (%s)",
currentLevel, expectedLevel));
}
return retrier.callWithRetry(() -> transactNoRetry(work), JpaRetries::isFailedTxnRetriable);
}
@Override
public <T> T transactNoRetry(Supplier<T> work) {
public <T> T transact(Supplier<T> work, TransactionIsolationLevel isolationLevel) {
// This prevents inner transaction from retrying, thus avoiding a cascade retry effect.
if (inTransaction()) {
return transactNoRetry(work, isolationLevel);
}
return retrier.callWithRetry(
() -> transactNoRetry(work, isolationLevel), JpaRetries::isFailedTxnRetriable);
}
@Override
public <T> T transact(Supplier<T> work) {
return transact(work, null);
}
@Override
public <T> T transactNoRetry(
Supplier<T> work, @Nullable TransactionIsolationLevel isolationLevel) {
if (inTransaction()) {
if (isolationLevel != null && getHibernatePerTransactionIsolationEnabled()) {
TransactionIsolationLevel enclosingLevel = getCurrentTransactionIsolationLevel();
if (isolationLevel != enclosingLevel) {
throw new IllegalStateException(
String.format(
"Isolation level conflict detected in nested transactions.\n"
+ "Enclosing transaction: %s\nCurrent transaction: %s",
enclosingLevel, isolationLevel));
}
}
return work.get();
}
TransactionInfo txnInfo = transactionInfo.get();
@@ -156,6 +185,18 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
try {
txn.begin();
txnInfo.start(clock);
if (isolationLevel != null) {
if (getHibernatePerTransactionIsolationEnabled()) {
getEntityManager()
.createNativeQuery(
String.format("SET TRANSACTION ISOLATION LEVEL %s", isolationLevel.getMode()))
.executeUpdate();
logger.atInfo().log("Running transaction at %s", isolationLevel);
} else {
logger.atWarning().log(
"Per-transaction isolation level disabled, but %s was requested", isolationLevel);
}
}
T result = work.get();
txn.commit();
return result;
@@ -174,21 +215,55 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
}
@Override
public void transact(Runnable work) {
public <T> T transactNoRetry(Supplier<T> work) {
return transactNoRetry(work, null);
}
@Override
public void transact(Runnable work, TransactionIsolationLevel isolationLevel) {
transact(
() -> {
work.run();
return null;
});
},
isolationLevel);
}
@Override
public void transactNoRetry(Runnable work) {
public void transact(Runnable work) {
transact(work, null);
}
@Override
public void transactNoRetry(Runnable work, TransactionIsolationLevel isolationLevel) {
transactNoRetry(
() -> {
work.run();
return null;
});
},
isolationLevel);
}
@Override
public void transactNoRetry(Runnable work) {
transactNoRetry(work, null);
}
@Override
public TransactionIsolationLevel getDefaultTransactionIsolationLevel() {
return TransactionIsolationLevel.valueOf(
(String) emf.getProperties().get(Environment.ISOLATION));
}
@Override
public TransactionIsolationLevel getCurrentTransactionIsolationLevel() {
assertInTransaction();
String mode =
(String)
getEntityManager()
.createNativeQuery("SHOW TRANSACTION ISOLATION LEVEL")
.getSingleResult();
return TransactionIsolationLevel.fromMode(mode);
}
@Override
@@ -287,7 +362,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
Integer.class)
.setMaxResults(1);
entityIds.forEach(entityId -> query.setParameter(entityId.name, entityId.value));
return query.getResultList().size() > 0;
return !query.getResultList().isEmpty();
}
@Override
@@ -506,7 +581,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
.collect(toImmutableSet());
}
private String getAndClause(ImmutableSet<EntityId> entityIds) {
private static String getAndClause(ImmutableSet<EntityId> entityIds) {
return entityIds.stream()
.map(entityId -> String.format("%s = :%s", entityId.name, entityId.name))
.collect(joining(" AND "));
@@ -556,7 +631,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
try {
getEntityManager().getMetamodel().entity(object.getClass());
} catch (IllegalArgumentException e) {
// The object is not an entity. Return without detaching.
// The object is not an entity. Return without detaching.
return object;
}
@@ -637,7 +712,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
TypedQuery<T> delegate;
public DetachingTypedQuery(TypedQuery<T> delegate) {
DetachingTypedQuery(TypedQuery<T> delegate) {
this.delegate = delegate;
}
@@ -866,7 +941,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public Optional<T> first() {
List<T> results = buildQuery().setMaxResults(1).getResultList();
return results.size() > 0 ? Optional.of(detach(results.get(0))) : Optional.empty();
return !results.isEmpty() ? Optional.of(detach(results.get(0))) : Optional.empty();
}
@Override
@@ -895,7 +970,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
public ImmutableList<T> list() {
return buildQuery().getResultList().stream()
.map(JpaTransactionManagerImpl.this::detach)
.collect(ImmutableList.toImmutableList());
.collect(toImmutableList());
}
}
}

View File

@@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.model.ImmutableObject;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.VKey;
import java.util.NoSuchElementException;
import java.util.Optional;
@@ -49,9 +50,18 @@ public interface TransactionManager {
/** Executes the work in a transaction and returns the result. */
<T> T transact(Supplier<T> work);
/**
* Executes the work in a transaction at the given {@link TransactionIsolationLevel} and returns
* the result.
*/
<T> T transact(Supplier<T> work, TransactionIsolationLevel isolationLevel);
/** Executes the work in a transaction. */
void transact(Runnable work);
/** Executes the work in a transaction at the given {@link TransactionIsolationLevel}. */
void transact(Runnable work, TransactionIsolationLevel isolationLevel);
/** Returns the time associated with the start of this particular transaction attempt. */
DateTime getTransactionTime();

View File

@@ -17,9 +17,9 @@ package google.registry.reporting.billing;
import static google.registry.reporting.ReportingModule.PARAM_YEAR_MONTH;
import static google.registry.request.Action.Method.POST;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import com.google.api.services.dataflow.Dataflow;
import com.google.api.services.dataflow.model.Job;
@@ -111,7 +111,7 @@ public class PublishInvoicesAction implements Runnable {
break;
default:
logger.atInfo().log("Job in non-terminal state %s, retrying:", state);
response.setStatus(SC_NOT_MODIFIED);
response.setStatus(SC_SERVICE_UNAVAILABLE);
break;
}
} catch (IOException e) {

View File

@@ -29,6 +29,7 @@ import com.google.common.net.MediaType;
import google.registry.batch.CloudTasksUtils;
import google.registry.bigquery.BigqueryJobFailureException;
import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GmailClient;
import google.registry.reporting.icann.IcannReportingModule.ReportType;
import google.registry.request.Action;
import google.registry.request.Action.Service;
@@ -37,7 +38,6 @@ import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.EmailMessage;
import google.registry.util.Retrier;
import google.registry.util.SendEmailService;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
@@ -83,8 +83,12 @@ public final class IcannReportingStagingAction implements Runnable {
@Inject Retrier retrier;
@Inject Response response;
@Inject @Config("gSuiteOutgoingEmailAddress") InternetAddress sender;
@Inject @Config("alertRecipientEmailAddress") InternetAddress recipient;
@Inject SendEmailService emailService;
@Inject
@Config("newAlertRecipientEmailAddress")
InternetAddress recipient;
@Inject GmailClient gmailClient;
@Inject CloudTasksUtils cloudTasksUtils;
@Inject IcannReportingStagingAction() {}
@@ -103,7 +107,7 @@ public final class IcannReportingStagingAction implements Runnable {
stager.createAndUploadManifest(subdir, manifestedFiles);
logger.atInfo().log("Completed staging %d report files.", manifestedFiles.size());
emailService.sendEmail(
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setSubject("ICANN Monthly report staging summary [SUCCESS]")
.setBody(
@@ -130,7 +134,7 @@ public final class IcannReportingStagingAction implements Runnable {
},
BigqueryJobFailureException.class);
} catch (Throwable e) {
emailService.sendEmail(
gmailClient.sendEmail(
EmailMessage.create(
"ICANN Monthly report staging summary [FAILURE]",
String.format(

View File

@@ -27,6 +27,7 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.io.ByteStreams;
import google.registry.config.RegistryConfig.Config;
import google.registry.gcs.GcsUtils;
import google.registry.groups.GmailClient;
import google.registry.model.common.Cursor;
import google.registry.model.common.Cursor.CursorType;
import google.registry.model.tld.Tld;
@@ -41,7 +42,6 @@ import google.registry.request.lock.LockHandler;
import google.registry.util.Clock;
import google.registry.util.EmailMessage;
import google.registry.util.Retrier;
import google.registry.util.SendEmailService;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
@@ -87,8 +87,12 @@ public final class IcannReportingUploadAction implements Runnable {
@Inject Retrier retrier;
@Inject Response response;
@Inject @Config("gSuiteOutgoingEmailAddress") InternetAddress sender;
@Inject @Config("alertRecipientEmailAddress") InternetAddress recipient;
@Inject SendEmailService emailService;
@Inject
@Config("newAlertRecipientEmailAddress")
InternetAddress recipient;
@Inject GmailClient gmailClient;
@Inject Clock clock;
@Inject LockHandler lockHandler;
@@ -293,7 +297,7 @@ public final class IcannReportingUploadAction implements Runnable {
(e) ->
String.format("%s - %s", e.getKey(), e.getValue() ? "SUCCESS" : "FAILURE"))
.collect(Collectors.joining("\n")));
emailService.sendEmail(EmailMessage.create(subject, body, recipient, sender));
gmailClient.sendEmail(EmailMessage.create(subject, body, recipient, sender));
}
private byte[] readBytesFromGcs(BlobId reportFilename) throws IOException {

View File

@@ -141,8 +141,7 @@ public class GenerateSpec11ReportAction implements Runnable {
jobId,
ReportingModule.PARAM_DATE,
date.toString()),
// TODO(b/296582836): mitigating retry problem. Remove `+10` when bug is fixed.
Duration.standardMinutes(ReportingModule.ENQUEUE_DELAY_MINUTES + 10)));
Duration.standardMinutes(ReportingModule.ENQUEUE_DELAY_MINUTES)));
}
response.setStatus(SC_OK);
response.setPayload(String.format("Launched Spec11 pipeline: %s", jobId));

View File

@@ -18,9 +18,9 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.reporting.ReportingModule.PARAM_DATE;
import static google.registry.request.Action.Method.POST;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import com.google.api.services.dataflow.Dataflow;
import com.google.api.services.dataflow.model.Job;
@@ -135,7 +135,7 @@ public class PublishSpec11ReportAction implements Runnable {
break;
default:
logger.atInfo().log("Job in non-terminal state %s, retrying:", state);
response.setStatus(SC_NOT_MODIFIED);
response.setStatus(SC_SERVICE_UNAVAILABLE);
break;
}
} catch (IOException | JSONException e) {

View File

@@ -31,28 +31,22 @@ import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.google.common.net.MediaType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.protobuf.ByteString;
import dagger.Module;
import dagger.Provides;
import google.registry.model.adapters.CurrencyJsonAdapter;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.UnsupportedMediaTypeException;
import google.registry.request.auth.AuthResult;
import google.registry.request.lock.LockHandler;
import google.registry.request.lock.LockHandlerImpl;
import google.registry.util.CidrAddressBlock;
import google.registry.util.CidrAddressBlock.CidrAddressBlockAdapter;
import google.registry.util.DateTimeTypeAdapter;
import google.registry.tools.GsonUtils;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
@@ -80,12 +74,7 @@ public final class RequestModule {
@VisibleForTesting
@Provides
public static Gson provideGson() {
return new GsonBuilder()
.registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter())
.registerTypeAdapter(CidrAddressBlock.class, new CidrAddressBlockAdapter())
.registerTypeAdapter(CurrencyUnit.class, new CurrencyJsonAdapter())
.excludeFieldsWithoutExposeAnnotation()
.create();
return GsonUtils.provideGson();
}
@Provides
@@ -265,7 +254,8 @@ public final class RequestModule {
@OptionalJsonPayload
public static Optional<JsonElement> provideJsonBody(HttpServletRequest req, Gson gson) {
try {
return Optional.of(gson.fromJson(req.getReader(), JsonElement.class));
// GET requests return a null reader and thus a null JsonObject, which is fine
return Optional.ofNullable(gson.fromJson(req.getReader(), JsonElement.class));
} catch (IOException e) {
return Optional.empty();
}

View File

@@ -16,23 +16,23 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import com.beust.jcommander.Parameters;
import google.registry.model.domain.token.PackagePromotion;
import google.registry.model.domain.token.BulkPricingPackage;
import org.jetbrains.annotations.Nullable;
/** Command to create a PackagePromotion */
/** Command to create a {@link BulkPricingPackage} */
@Parameters(
separators = " =",
commandDescription =
"Create new package promotion object(s) for registrars to register multiple names under"
+ " one contractual annual package price using a package allocation token")
public final class CreatePackagePromotionCommand extends CreateOrUpdatePackagePromotionCommand {
"Create new bulk pricing package object(s) for registrars to register multiple names under"
+ " one contractual annual bulk price using a bulk pricing allocation token")
public final class CreateBulkPricingPackageCommand extends CreateOrUpdateBulkPricingPackageCommand {
@Nullable
@Override
PackagePromotion getOldPackagePromotion(String tokenString) {
BulkPricingPackage getOldBulkPricingPackage(String tokenString) {
checkArgument(
!PackagePromotion.loadByTokenString(tokenString).isPresent(),
"PackagePromotion with token %s already exists",
!BulkPricingPackage.loadByTokenString(tokenString).isPresent(),
"BulkPricingPackage with token %s already exists",
tokenString);
return null;
}

View File

@@ -20,7 +20,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import com.beust.jcommander.Parameter;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.PackagePromotion;
import google.registry.model.domain.token.BulkPricingPackage;
import google.registry.persistence.VKey;
import java.util.Date;
import java.util.List;
@@ -29,39 +29,40 @@ import javax.annotation.Nullable;
import org.joda.money.Money;
import org.joda.time.DateTime;
/** Shared base class for commands to create or update a PackagePromotion object. */
abstract class CreateOrUpdatePackagePromotionCommand extends MutatingCommand {
/** Shared base class for commands to create or update a {@link BulkPricingPackage} object. */
abstract class CreateOrUpdateBulkPricingPackageCommand extends MutatingCommand {
@Parameter(description = "Allocation token String of the package token", required = true)
@Parameter(description = "Allocation token String of the bulk token", required = true)
List<String> mainParameters;
@Nullable
@Parameter(
names = {"-d", "--max_domains"},
description = "Maximum concurrent active domains allowed in the package")
description = "Maximum concurrent active domains allowed in the bulk pricing package")
Integer maxDomains;
@Nullable
@Parameter(
names = {"-c", "--max_creates"},
description = "Maximum domain creations allowed in the package each year")
description = "Maximum domain creations allowed in the bulk pricing package each year")
Integer maxCreates;
@Nullable
@Parameter(
names = {"-p", "--price"},
description = "Annual price of the package")
description = "Annual price of the bulk pricing package")
Money price;
@Nullable
@Parameter(
names = "--next_billing_date",
description = "The next date that the package should be billed for its annual fee")
description =
"The next date that the bulk pricing package should be billed for its annual fee")
Date nextBillingDate;
/** Returns the existing PackagePromotion or null if it does not exist. */
/** Returns the existing BulkPricingPackage or null if it does not exist. */
@Nullable
abstract PackagePromotion getOldPackagePromotion(String token);
abstract BulkPricingPackage getOldBulkPricingPackage(String token);
/** Returns the allocation token object. */
AllocationToken getAndCheckAllocationToken(String token) {
@@ -69,12 +70,12 @@ abstract class CreateOrUpdatePackagePromotionCommand extends MutatingCommand {
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(AllocationToken.class, token)));
checkArgument(
allocationToken.isPresent(),
"An allocation token with the token String %s does not exist. The package token must be"
+ " created first before it can be used to create a PackagePromotion",
"An allocation token with the token String %s does not exist. The bulk token must be"
+ " created first before it can be used to create a BulkPricingPackage",
token);
checkArgument(
allocationToken.get().getTokenType().equals(TokenType.PACKAGE),
"The allocation token must be of the PACKAGE token type");
allocationToken.get().getTokenType().equals(TokenType.BULK_PRICING),
"The allocation token must be of the BULK_PRICING token type");
return allocationToken.get();
}
@@ -88,21 +89,21 @@ abstract class CreateOrUpdatePackagePromotionCommand extends MutatingCommand {
for (String token : mainParameters) {
tm().transact(
() -> {
PackagePromotion oldPackage = getOldPackagePromotion(token);
BulkPricingPackage oldBulkPricingPackage = getOldBulkPricingPackage(token);
checkArgument(
oldPackage != null || price != null,
"PackagePrice is required when creating a new package");
oldBulkPricingPackage != null || price != null,
"BulkPrice is required when creating a new bulk pricing package");
AllocationToken allocationToken = getAndCheckAllocationToken(token);
PackagePromotion.Builder builder =
(oldPackage == null)
? new PackagePromotion.Builder().setToken(allocationToken)
: oldPackage.asBuilder();
BulkPricingPackage.Builder builder =
(oldBulkPricingPackage == null)
? new BulkPricingPackage.Builder().setToken(allocationToken)
: oldBulkPricingPackage.asBuilder();
Optional.ofNullable(maxDomains).ifPresent(builder::setMaxDomains);
Optional.ofNullable(maxCreates).ifPresent(builder::setMaxCreates);
Optional.ofNullable(price).ifPresent(builder::setPackagePrice);
Optional.ofNullable(price).ifPresent(builder::setBulkPrice);
Optional.ofNullable(nextBillingDate)
.ifPresent(
nextBillingDate ->
@@ -110,8 +111,8 @@ abstract class CreateOrUpdatePackagePromotionCommand extends MutatingCommand {
if (clearLastNotificationSent()) {
builder.setLastNotificationSent(null);
}
PackagePromotion newPackage = builder.build();
stageEntityChange(oldPackage, newPackage);
BulkPricingPackage newBUlkPricingPackage = builder.build();
stageEntityChange(oldBulkPricingPackage, newBUlkPricingPackage);
});
}
}

View File

@@ -18,8 +18,8 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.difference;
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.DEFAULT;
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
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.persistence.transaction.TransactionManagerFactory.tm;
@@ -279,19 +279,20 @@ class GenerateAllocationTokensCommand implements Command {
}
if (!isNullOrEmpty(tokenStatusTransitions)) {
// Don't allow package tokens to be created with a scheduled end time since this could allow
// future domains to be attributed to the package and never be billed. Package promotion
// Don't allow bulk tokens to be created with a scheduled end time since this could allow
// future domains to be attributed to the bulk pricing package and never be billed. Bulk
// tokens should only be scheduled to end with a brief time period before the status
// transition occurs so that no new domains are registered using that token between when the
// status is scheduled and when the transition occurs.
// TODO(@sarahbot): Create a cleaner way to handle ending packages once we actually have
// customers using them
// TODO(@sarahbot): Create a cleaner way to handle ending bulk pricing packages once we
// actually have customers using them
boolean hasEnding =
tokenStatusTransitions.containsValue(TokenStatus.ENDED)
|| tokenStatusTransitions.containsValue(TokenStatus.CANCELLED);
checkArgument(
!(PACKAGE.equals(tokenType) && hasEnding),
"PACKAGE tokens should not be generated with ENDED or CANCELLED in their transition map");
!(BULK_PRICING.equals(tokenType) && hasEnding),
"BULK_PRICING tokens should not be generated with ENDED or CANCELLED in their transition"
+ " map");
}
if (tokenStrings != null) {

View File

@@ -19,14 +19,14 @@ import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.domain.token.PackagePromotion;
import google.registry.model.domain.token.BulkPricingPackage;
import java.util.List;
/** Command to show a {@link PackagePromotion} object. */
@Parameters(separators = " =", commandDescription = "Show package promotion object(s)")
public class GetPackagePromotionCommand extends GetEppResourceCommand {
/** Command to show a {@link BulkPricingPackage} object. */
@Parameters(separators = " =", commandDescription = "Show bulk pricing package object(s)")
public class GetBulkPricingPackageCommand extends GetEppResourceCommand {
@Parameter(description = "Package token(s)", required = true)
@Parameter(description = "Bulk pricing token(s)", required = true)
private List<String> mainParameters;
@Override
@@ -34,12 +34,12 @@ public class GetPackagePromotionCommand extends GetEppResourceCommand {
for (String token : mainParameters) {
tm().transact(
() -> {
PackagePromotion packagePromotion =
BulkPricingPackage bulkPricingPackage =
checkArgumentPresent(
PackagePromotion.loadByTokenString(token),
"PackagePromotion with package token %s does not exist",
BulkPricingPackage.loadByTokenString(token),
"BulkPricingPackage with token %s does not exist",
token);
System.out.println(packagePromotion);
System.out.println(bulkPricingPackage);
});
}
}

View File

@@ -0,0 +1,81 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import google.registry.model.adapters.CurrencyJsonAdapter;
import google.registry.util.CidrAddressBlock;
import google.registry.util.CidrAddressBlock.CidrAddressBlockAdapter;
import google.registry.util.DateTimeTypeAdapter;
import java.io.IOException;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
/** Utility class for methods related to GSON and necessary GSON processing. */
public class GsonUtils {
/** Interface to enable GSON post-processing on a particular object after deserialization. */
public interface GsonPostProcessable {
void postProcess();
}
/**
* Some objects may require post-processing after deserialization from JSON.
*
* <p>We do this upon deserialization in order to make sure that the object matches the format
* that we expect to be stored in the database. See {@link
* google.registry.model.eppcommon.Address} for an example.
*/
public static class GsonPostProcessableTypeAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
TypeAdapter<T> originalAdapter = gson.getDelegateAdapter(this, type);
if (!GsonPostProcessable.class.isAssignableFrom(type.getRawType())) {
return originalAdapter;
}
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter out, T value) throws IOException {
originalAdapter.write(out, value);
}
@Override
public T read(JsonReader in) throws IOException {
T t = originalAdapter.read(in);
((GsonPostProcessable) t).postProcess();
return t;
}
};
}
}
public static Gson provideGson() {
return new GsonBuilder()
.registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter())
.registerTypeAdapter(CidrAddressBlock.class, new CidrAddressBlockAdapter())
.registerTypeAdapter(CurrencyUnit.class, new CurrencyJsonAdapter())
.registerTypeAdapterFactory(new GsonPostProcessableTypeAdapterFactory())
.excludeFieldsWithoutExposeAnnotation()
.create();
}
private GsonUtils() {}
}

View File

@@ -36,6 +36,7 @@ public final class RegistryTool {
.put("convert_idn", ConvertIdnCommand.class)
.put("count_domains", CountDomainsCommand.class)
.put("create_anchor_tenant", CreateAnchorTenantCommand.class)
.put("create_bulk_pricing_package", CreateBulkPricingPackageCommand.class)
.put(
"create_cancellations_for_billing_events",
CreateCancellationsForBillingEventsCommand.class)
@@ -43,7 +44,6 @@ public final class RegistryTool {
.put("create_contact", CreateContactCommand.class)
.put("create_domain", CreateDomainCommand.class)
.put("create_host", CreateHostCommand.class)
.put("create_package_promotion", CreatePackagePromotionCommand.class)
.put("create_premium_list", CreatePremiumListCommand.class)
.put("create_registrar", CreateRegistrarCommand.class)
.put("create_registrar_groups", CreateRegistrarGroupsCommand.class)
@@ -67,13 +67,13 @@ public final class RegistryTool {
.put("generate_lordn", GenerateLordnCommand.class)
.put("generate_zone_files", GenerateZoneFilesCommand.class)
.put("get_allocation_token", GetAllocationTokenCommand.class)
.put("get_bulk_pricing_package", GetBulkPricingPackageCommand.class)
.put("get_claims_list", GetClaimsListCommand.class)
.put("get_contact", GetContactCommand.class)
.put("get_domain", GetDomainCommand.class)
.put("get_history_entries", GetHistoryEntriesCommand.class)
.put("get_host", GetHostCommand.class)
.put("get_keyring_secret", GetKeyringSecretCommand.class)
.put("get_package_promotion", GetPackagePromotionCommand.class)
.put("get_premium_list", GetPremiumListCommand.class)
.put("get_registrar", GetRegistrarCommand.class)
.put("get_reserved_list", GetReservedListCommand.class)
@@ -104,10 +104,10 @@ public final class RegistryTool {
.put("unlock_domain", UnlockDomainCommand.class)
.put("unrenew_domain", UnrenewDomainCommand.class)
.put("update_allocation_tokens", UpdateAllocationTokensCommand.class)
.put("update_bulk_pricing_package", UpdateBulkPricingPackageCommand.class)
.put("update_cursors", UpdateCursorsCommand.class)
.put("update_domain", UpdateDomainCommand.class)
.put("update_keyring_secret", UpdateKeyringSecretCommand.class)
.put("update_package_promotion", UpdatePackagePromotionCommand.class)
.put("update_premium_list", UpdatePremiumListCommand.class)
.put("update_recurrence", UpdateRecurrenceCommand.class)
.put("update_registrar", UpdateRegistrarCommand.class)

View File

@@ -114,18 +114,19 @@ interface RegistryToolComponent {
void inject(GenerateEscrowDepositCommand command);
void inject(GetBulkPricingPackageCommand command);
void inject(GetContactCommand command);
void inject(GetDomainCommand command);
void inject(GetHostCommand command);
void inject(GetPackagePromotionCommand command);
void inject(GetKeyringSecretCommand command);
void inject(GetSqlCredentialCommand command);
void inject(GetTldCommand command);
void inject(GhostrydeCommand command);
void inject(ListCursorsCommand command);

View File

@@ -169,18 +169,18 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
}
private AllocationToken updateToken(AllocationToken original) {
if (endToken && original.getTokenType().equals(TokenType.PACKAGE)) {
Long domainsInPackage =
tm().query("SELECT COUNT(*) FROM Domain WHERE currentPackageToken = :token", Long.class)
if (endToken && original.getTokenType().equals(TokenType.BULK_PRICING)) {
Long domainsInBulkPackage =
tm().query("SELECT COUNT(*) FROM Domain WHERE currentBulkToken = :token", Long.class)
.setParameter("token", original.createVKey())
.getSingleResult();
checkArgument(
domainsInPackage == 0,
"Package token %s can not end its promotion because it still has %s domains in the"
+ " package",
domainsInBulkPackage == 0,
"Bulk token %s can not end its promotion because it still has %s domains in the"
+ " promotion",
original.getToken(),
domainsInPackage);
domainsInBulkPackage);
}
AllocationToken.Builder builder = original.asBuilder();
Optional.ofNullable(allowedClientIds)

View File

@@ -17,12 +17,12 @@ import static com.google.common.base.Preconditions.checkArgument;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.domain.token.PackagePromotion;
import google.registry.model.domain.token.BulkPricingPackage;
import java.util.Optional;
/** Command to update a PackagePromotion */
@Parameters(separators = " =", commandDescription = "Update package promotion object(s)")
public final class UpdatePackagePromotionCommand extends CreateOrUpdatePackagePromotionCommand {
/** Command to update a {@link BulkPricingPackage} */
@Parameters(separators = " =", commandDescription = "Update bulk pricing package object(s)")
public final class UpdateBulkPricingPackageCommand extends CreateOrUpdateBulkPricingPackageCommand {
@Parameter(
names = "--clear_last_notification_sent",
@@ -32,10 +32,14 @@ public final class UpdatePackagePromotionCommand extends CreateOrUpdatePackagePr
boolean clearLastNotificationSent;
@Override
PackagePromotion getOldPackagePromotion(String token) {
Optional<PackagePromotion> oldPackage = PackagePromotion.loadByTokenString(token);
checkArgument(oldPackage.isPresent(), "PackagePromotion with token %s does not exist", token);
return oldPackage.get();
BulkPricingPackage getOldBulkPricingPackage(String token) {
Optional<BulkPricingPackage> oldBulkPricingPackage =
BulkPricingPackage.loadByTokenString(token);
checkArgument(
oldBulkPricingPackage.isPresent(),
"BulkPricingPackage with token %s does not exist",
token);
return oldBulkPricingPackage.get();
}
@Override

View File

@@ -131,7 +131,7 @@ public class ContactAction implements JsonGetAction {
oldContacts,
Collections.singletonMap(
"contacts",
contacts.get().stream().map(c -> c.toJsonMap()).collect(toImmutableList())));
contacts.get().stream().map(RegistrarPoc::toJsonMap).collect(toImmutableList())));
try {
RegistrarSettingsAction.checkContactRequirements(oldContacts, updatedContacts);
} catch (FormException e) {

View File

@@ -0,0 +1,107 @@
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server.console.settings;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import com.google.api.client.http.HttpStatusCodes;
import com.google.gson.Gson;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.ui.server.registrar.JsonGetAction;
import java.util.Optional;
import javax.inject.Inject;
/**
* Console action for editing fields on a registrar that are visible in WHOIS/RDAP.
*
* <p>This doesn't cover many of the registrar fields but rather only those that are visible in
* WHOIS/RDAP and don't have any other obvious means of edit.
*/
@Action(
service = Action.Service.DEFAULT,
path = WhoisRegistrarFieldsAction.PATH,
method = {POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class WhoisRegistrarFieldsAction implements JsonGetAction {
static final String PATH = "/console-api/settings/whois-fields";
private final AuthResult authResult;
private final Response response;
private final Gson gson;
private AuthenticatedRegistrarAccessor registrarAccessor;
private Optional<Registrar> registrar;
@Inject
public WhoisRegistrarFieldsAction(
AuthResult authResult,
Response response,
Gson gson,
AuthenticatedRegistrarAccessor registrarAccessor,
@Parameter("registrar") Optional<Registrar> registrar) {
this.authResult = authResult;
this.response = response;
this.gson = gson;
this.registrarAccessor = registrarAccessor;
this.registrar = registrar;
}
@Override
public void run() {
if (!registrar.isPresent()) {
response.setStatus(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);
response.setPayload(gson.toJson("'registrar' parameter is not present"));
return;
}
User user = authResult.userAuthInfo().get().consoleUser().get();
if (!user.getUserRoles()
.hasPermission(
registrar.get().getRegistrarId(), ConsolePermission.EDIT_REGISTRAR_DETAILS)) {
response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
return;
}
tm().transact(() -> loadAndModifyRegistrar(registrar.get()));
}
private void loadAndModifyRegistrar(Registrar providedRegistrar) {
Registrar savedRegistrar;
try {
// reload to make sure the object has all the correct fields
savedRegistrar = registrarAccessor.getRegistrar(providedRegistrar.getRegistrarId());
} catch (RegistrarAccessDeniedException e) {
response.setStatus(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
response.setPayload(e.getMessage());
return;
}
Registrar.Builder newRegistrar = savedRegistrar.asBuilder();
newRegistrar.setWhoisServer(providedRegistrar.getWhoisServer());
newRegistrar.setUrl(providedRegistrar.getUrl());
newRegistrar.setLocalizedAddress(providedRegistrar.getLocalizedAddress());
tm().put(newRegistrar.build());
response.setStatus(HttpStatusCodes.STATUS_CODE_OK);
}
}

View File

@@ -31,6 +31,7 @@ import com.google.common.flogger.FluentLogger;
import com.google.gson.Gson;
import google.registry.config.RegistryConfig.Config;
import google.registry.flows.domain.DomainFlowUtils;
import google.registry.groups.GmailClient;
import google.registry.model.domain.RegistryLock;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
@@ -46,7 +47,6 @@ import google.registry.request.auth.UserAuthInfo;
import google.registry.security.JsonResponseHelper;
import google.registry.tools.DomainLockUtils;
import google.registry.util.EmailMessage;
import google.registry.util.SendEmailService;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Optional;
@@ -83,7 +83,7 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
private final JsonActionRunner jsonActionRunner;
private final AuthResult authResult;
private final AuthenticatedRegistrarAccessor registrarAccessor;
private final SendEmailService sendEmailService;
private final GmailClient gmailClient;
private final DomainLockUtils domainLockUtils;
private final InternetAddress gSuiteOutgoingEmailAddress;
@@ -93,14 +93,14 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
JsonActionRunner jsonActionRunner,
AuthResult authResult,
AuthenticatedRegistrarAccessor registrarAccessor,
SendEmailService sendEmailService,
GmailClient gmailClient,
DomainLockUtils domainLockUtils,
@Config("gSuiteOutgoingEmailAddress") InternetAddress gSuiteOutgoingEmailAddress) {
this.req = req;
this.jsonActionRunner = jsonActionRunner;
this.authResult = authResult;
this.registrarAccessor = registrarAccessor;
this.sendEmailService = sendEmailService;
this.gmailClient = gmailClient;
this.domainLockUtils = domainLockUtils;
this.gSuiteOutgoingEmailAddress = gSuiteOutgoingEmailAddress;
}
@@ -168,7 +168,7 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
ImmutableList<InternetAddress> recipients =
ImmutableList.of(new InternetAddress(userEmail, true));
String action = isLock ? "lock" : "unlock";
sendEmailService.sendEmail(
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setBody(body)
.setSubject(String.format("Registry %s verification", action))

View File

@@ -53,7 +53,7 @@
<class>google.registry.model.domain.secdns.DomainDsData</class>
<class>google.registry.model.domain.secdns.DomainDsDataHistory</class>
<class>google.registry.model.domain.token.AllocationToken</class>
<class>google.registry.model.domain.token.PackagePromotion</class>
<class>google.registry.model.domain.token.BulkPricingPackage</class>
<class>google.registry.model.host.HostHistory</class>
<class>google.registry.model.host.Host</class>
<class>google.registry.model.poll.PollMessage</class>

View File

@@ -32,7 +32,7 @@ import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
import google.registry.model.contact.Contact;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.PackagePromotion;
import google.registry.model.domain.token.BulkPricingPackage;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.DatabaseHelper;
@@ -53,8 +53,8 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
/** Unit tests for {@link CheckPackagesComplianceAction}. */
public class CheckPackagesComplianceActionTest {
/** Unit tests for {@link CheckBulkComplianceAction}. */
public class CheckBulkComplianceActionTest {
// This is the default creation time for test data.
private final FakeClock clock = new FakeClock(DateTime.parse("2012-03-25TZ"));
private static final String CREATE_LIMIT_EMAIL_SUBJECT = "create limit subject";
@@ -72,14 +72,14 @@ public class CheckPackagesComplianceActionTest {
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
private CheckPackagesComplianceAction action;
private CheckBulkComplianceAction action;
private AllocationToken token;
private final TestLogHandler logHandler = new TestLogHandler();
private final Logger loggerToIntercept =
Logger.getLogger(CheckPackagesComplianceAction.class.getCanonicalName());
Logger.getLogger(CheckBulkComplianceAction.class.getCanonicalName());
private final SendEmailService emailService = mock(SendEmailService.class);
private Contact contact;
private PackagePromotion packagePromotion;
private BulkPricingPackage bulkPricingPackage;
private SendEmailUtils sendEmailUtils;
private ArgumentCaptor<EmailMessage> emailCaptor = ArgumentCaptor.forClass(EmailMessage.class);
@@ -94,7 +94,7 @@ public class CheckPackagesComplianceActionTest {
emailService);
createTld("tld");
action =
new CheckPackagesComplianceAction(
new CheckBulkComplianceAction(
sendEmailUtils,
clock,
CREATE_LIMIT_EMAIL_SUBJECT,
@@ -108,19 +108,19 @@ public class CheckPackagesComplianceActionTest {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setDiscountFraction(1)
.build());
packagePromotion =
new PackagePromotion.Builder()
bulkPricingPackage =
new BulkPricingPackage.Builder()
.setToken(token)
.setMaxDomains(3)
.setMaxCreates(1)
.setPackagePrice(Money.of(CurrencyUnit.USD, 1000))
.setBulkPrice(Money.of(CurrencyUnit.USD, 1000))
.setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z"))
.setLastNotificationSent(DateTime.parse("2010-11-12T05:00:00Z"))
.build();
@@ -134,46 +134,48 @@ public class CheckPackagesComplianceActionTest {
}
@Test
void testSuccess_noPackageOverCreateLimit() {
tm().transact(() -> tm().put(packagePromotion));
void testSuccess_noBulkPackageOverCreateLimit() {
tm().transact(() -> tm().put(bulkPricingPackage));
persistEppResource(
DatabaseHelper.newDomain("foo.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
action.run();
verifyNoInteractions(emailService);
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Found no packages over their create limit.");
.hasLogAtLevelWithMessage(
Level.INFO, "Found no bulk pricing packages over their create limit.");
}
@Test
void testSuccess_onePackageOverCreateLimit() throws Exception {
tm().transact(() -> tm().put(packagePromotion));
void testSuccess_oneBulkPackageOverCreateLimit() throws Exception {
tm().transact(() -> tm().put(bulkPricingPackage));
// Create limit is 1, creating 2 domains to go over the limit
persistEppResource(
DatabaseHelper.newDomain("foo.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
persistEppResource(
DatabaseHelper.newDomain("buzz.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
action.run();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Found 1 packages over their create limit.");
.hasLogAtLevelWithMessage(
Level.INFO, "Found 1 bulk pricing packages over their create limit.");
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Package with package token abc123 has exceeded their max domain creation limit by 1"
+ " name(s).");
"Bulk pricing package with bulk token abc123 has exceeded their max domain creation"
+ " limit by 1 name(s).");
verify(emailService).sendEmail(emailCaptor.capture());
EmailMessage emailMessage = emailCaptor.getValue();
assertThat(emailMessage.subject()).isEqualTo(CREATE_LIMIT_EMAIL_SUBJECT);
@@ -182,92 +184,93 @@ public class CheckPackagesComplianceActionTest {
}
@Test
void testSuccess_multiplePackagesOverCreateLimit() {
tm().transact(() -> tm().put(packagePromotion));
void testSuccess_multipleBulkPricingPackagesOverCreateLimit() {
tm().transact(() -> tm().put(bulkPricingPackage));
// Create limit is 1, creating 2 domains to go over the limit
persistEppResource(
DatabaseHelper.newDomain("foo.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
persistEppResource(
DatabaseHelper.newDomain("buzz.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
AllocationToken token2 =
persistResource(
new AllocationToken.Builder()
.setToken("token")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setDiscountFraction(1)
.build());
PackagePromotion packagePromotion2 =
new PackagePromotion.Builder()
BulkPricingPackage bulkPricingPackage2 =
new BulkPricingPackage.Builder()
.setToken(token2)
.setMaxDomains(8)
.setMaxCreates(1)
.setPackagePrice(Money.of(CurrencyUnit.USD, 1000))
.setBulkPrice(Money.of(CurrencyUnit.USD, 1000))
.setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z"))
.build();
tm().transact(() -> tm().put(packagePromotion2));
tm().transact(() -> tm().put(bulkPricingPackage2));
persistEppResource(
DatabaseHelper.newDomain("foo2.tld", contact)
.asBuilder()
.setCurrentPackageToken(token2.createVKey())
.setCurrentBulkToken(token2.createVKey())
.build());
persistEppResource(
DatabaseHelper.newDomain("buzz2.tld", contact)
.asBuilder()
.setCurrentPackageToken(token2.createVKey())
.setCurrentBulkToken(token2.createVKey())
.build());
action.run();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Found 2 packages over their create limit.");
.hasLogAtLevelWithMessage(
Level.INFO, "Found 2 bulk pricing packages over their create limit.");
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Package with package token abc123 has exceeded their max domain creation limit by 1"
+ " name(s).");
"Bulk pricing package with bulk token abc123 has exceeded their max domain creation"
+ " limit by 1 name(s).");
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Package with package token token has exceeded their max domain creation limit by 1"
+ " name(s).");
"Bulk pricing package with bulk token token has exceeded their max domain creation"
+ " limit by 1 name(s).");
verify(emailService, times(2)).sendEmail(any(EmailMessage.class));
}
@Test
void testSuccess_onlyChecksCurrentBillingYear() {
tm().transact(() -> tm().put(packagePromotion));
tm().transact(() -> tm().put(bulkPricingPackage));
AllocationToken token2 =
persistResource(
new AllocationToken.Builder()
.setToken("token")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setDiscountFraction(1)
.build());
PackagePromotion packagePromotion2 =
new PackagePromotion.Builder()
BulkPricingPackage packagePromotion2 =
new BulkPricingPackage.Builder()
.setToken(token2)
.setMaxDomains(8)
.setMaxCreates(1)
.setPackagePrice(Money.of(CurrencyUnit.USD, 1000))
.setBulkPrice(Money.of(CurrencyUnit.USD, 1000))
.setNextBillingDate(DateTime.parse("2015-11-12T05:00:00Z"))
.build();
tm().transact(() -> tm().put(packagePromotion2));
@@ -276,289 +279,300 @@ public class CheckPackagesComplianceActionTest {
persistEppResource(
DatabaseHelper.newDomain("foo.tld", contact)
.asBuilder()
.setCurrentPackageToken(token2.createVKey())
.setCurrentBulkToken(token2.createVKey())
.build());
persistEppResource(
DatabaseHelper.newDomain("buzz.tld", contact)
.asBuilder()
.setCurrentPackageToken(token2.createVKey())
.setCurrentBulkToken(token2.createVKey())
.build());
action.run();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Found no packages over their create limit.");
.hasLogAtLevelWithMessage(
Level.INFO, "Found no bulk pricing packages over their create limit.");
verifyNoInteractions(emailService);
}
@Test
void testSuccess_noPackageOverActiveDomainsLimit() {
tm().transact(() -> tm().put(packagePromotion));
void testSuccess_noBulkPricingPackageOverActiveDomainsLimit() {
tm().transact(() -> tm().put(bulkPricingPackage));
persistEppResource(
DatabaseHelper.newDomain("foo.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
action.run();
verifyNoInteractions(emailService);
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Found no packages over their active domains limit.");
.hasLogAtLevelWithMessage(
Level.INFO, "Found no bulk pricing packages over their active domains limit.");
}
@Test
void testSuccess_onePackageOverActiveDomainsLimit() {
packagePromotion = packagePromotion.asBuilder().setMaxCreates(4).setMaxDomains(1).build();
tm().transact(() -> tm().put(packagePromotion));
void testSuccess_oneBulkPricingPackageOverActiveDomainsLimit() {
bulkPricingPackage = bulkPricingPackage.asBuilder().setMaxCreates(4).setMaxDomains(1).build();
tm().transact(() -> tm().put(bulkPricingPackage));
// Domains limit is 1, creating 2 domains to go over the limit
persistEppResource(
DatabaseHelper.newDomain("foo.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
persistEppResource(
DatabaseHelper.newDomain("buzz.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
AllocationToken token2 =
persistResource(
new AllocationToken.Builder()
.setToken("token")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setDiscountFraction(1)
.build());
PackagePromotion packagePromotion2 =
new PackagePromotion.Builder()
BulkPricingPackage bulkPricingPackage2 =
new BulkPricingPackage.Builder()
.setToken(token2)
.setMaxDomains(8)
.setMaxCreates(4)
.setPackagePrice(Money.of(CurrencyUnit.USD, 1000))
.setBulkPrice(Money.of(CurrencyUnit.USD, 1000))
.setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z"))
.build();
tm().transact(() -> tm().put(packagePromotion2));
tm().transact(() -> tm().put(bulkPricingPackage2));
persistEppResource(
DatabaseHelper.newDomain("foo2.tld", contact)
.asBuilder()
.setCurrentPackageToken(token2.createVKey())
.setCurrentBulkToken(token2.createVKey())
.build());
action.run();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Found 1 packages over their active domains limit.");
.hasLogAtLevelWithMessage(
Level.INFO, "Found 1 bulk pricing packages over their active domains limit.");
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Package with package token abc123 has exceed their max active domains limit by 1"
+ " name(s).");
"Bulk pricing package with bulk token abc123 has exceed their max active domains limit"
+ " by 1 name(s).");
verify(emailService).sendEmail(emailCaptor.capture());
EmailMessage emailMessage = emailCaptor.getValue();
assertThat(emailMessage.subject()).isEqualTo(DOMAIN_LIMIT_WARNING_EMAIL_SUBJECT);
assertThat(emailMessage.body())
.isEqualTo(
String.format(DOMAIN_LIMIT_WARNING_EMAIL_BODY, 1, "abc123", "The Registrar", 1, 2));
PackagePromotion packageAfterCheck =
tm().transact(() -> PackagePromotion.loadByTokenString(token.getToken()).get());
BulkPricingPackage packageAfterCheck =
tm().transact(() -> BulkPricingPackage.loadByTokenString(token.getToken()).get());
assertThat(packageAfterCheck.getLastNotificationSent().get()).isEqualTo(clock.nowUtc());
}
@Test
void testSuccess_multiplePackagesOverActiveDomainsLimit() {
void testSuccess_multipleBulkPricingPackagesOverActiveDomainsLimit() {
tm().transact(
() -> tm().put(packagePromotion.asBuilder().setMaxDomains(1).setMaxCreates(4).build()));
() ->
tm().put(bulkPricingPackage.asBuilder().setMaxDomains(1).setMaxCreates(4).build()));
// Domains limit is 1, creating 2 domains to go over the limit
persistEppResource(
DatabaseHelper.newDomain("foo.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
persistEppResource(
DatabaseHelper.newDomain("buzz.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
AllocationToken token2 =
persistResource(
new AllocationToken.Builder()
.setToken("token")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setDiscountFraction(1)
.build());
PackagePromotion packagePromotion2 =
new PackagePromotion.Builder()
BulkPricingPackage bulkPricingPackage2 =
new BulkPricingPackage.Builder()
.setToken(token2)
.setMaxDomains(1)
.setMaxCreates(5)
.setPackagePrice(Money.of(CurrencyUnit.USD, 1000))
.setBulkPrice(Money.of(CurrencyUnit.USD, 1000))
.setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z"))
.build();
tm().transact(() -> tm().put(packagePromotion2));
tm().transact(() -> tm().put(bulkPricingPackage2));
persistEppResource(
DatabaseHelper.newDomain("foo2.tld", contact)
.asBuilder()
.setCurrentPackageToken(token2.createVKey())
.setCurrentBulkToken(token2.createVKey())
.build());
persistEppResource(
DatabaseHelper.newDomain("buzz2.tld", contact)
.asBuilder()
.setCurrentPackageToken(token2.createVKey())
.setCurrentBulkToken(token2.createVKey())
.build());
action.run();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Found 2 packages over their active domains limit.");
.hasLogAtLevelWithMessage(
Level.INFO, "Found 2 bulk pricing packages over their active domains limit.");
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Package with package token abc123 has exceed their max active domains limit by 1"
+ " name(s).");
"Bulk pricing package with bulk token abc123 has exceed their max active domains limit"
+ " by 1 name(s).");
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Package with package token token has exceed their max active domains limit by 1"
+ " name(s).");
"Bulk pricing package with bulk token token has exceed their max active domains limit"
+ " by 1 name(s).");
verify(emailService, times(2)).sendEmail(any(EmailMessage.class));
}
@Test
void testSuccess_packageOverActiveDomainsLimitAlreadySentWarningEmail_DoesNotSendAgain() {
packagePromotion =
packagePromotion
void
testSuccess_bulkPricingPackageOverActiveDomainsLimitAlreadySentWarningEmail_DoesNotSendAgain() {
bulkPricingPackage =
bulkPricingPackage
.asBuilder()
.setMaxCreates(4)
.setMaxDomains(1)
.setLastNotificationSent(clock.nowUtc().minusDays(5))
.build();
tm().transact(() -> tm().put(packagePromotion));
tm().transact(() -> tm().put(bulkPricingPackage));
// Domains limit is 1, creating 2 domains to go over the limit
persistEppResource(
DatabaseHelper.newDomain("foo.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
persistEppResource(
DatabaseHelper.newDomain("buzz.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
action.run();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Found 1 packages over their active domains limit.");
.hasLogAtLevelWithMessage(
Level.INFO, "Found 1 bulk pricing packages over their active domains limit.");
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Package with package token abc123 has exceed their max active domains limit by 1"
+ " name(s).");
"Bulk pricing package with bulk token abc123 has exceed their max active domains limit"
+ " by 1 name(s).");
verifyNoInteractions(emailService);
PackagePromotion packageAfterCheck =
tm().transact(() -> PackagePromotion.loadByTokenString(token.getToken()).get());
BulkPricingPackage packageAfterCheck =
tm().transact(() -> BulkPricingPackage.loadByTokenString(token.getToken()).get());
assertThat(packageAfterCheck.getLastNotificationSent().get())
.isEqualTo(clock.nowUtc().minusDays(5));
}
@Test
void testSuccess_packageOverActiveDomainsLimitAlreadySentWarningEmailOver40DaysAgo_SendsAgain() {
packagePromotion =
packagePromotion
void
testSuccess_bulkPricingPackageOverActiveDomainsLimitAlreadySentWarningEmailOver40DaysAgo_SendsAgain() {
bulkPricingPackage =
bulkPricingPackage
.asBuilder()
.setMaxCreates(4)
.setMaxDomains(1)
.setLastNotificationSent(clock.nowUtc().minusDays(45))
.build();
tm().transact(() -> tm().put(packagePromotion));
tm().transact(() -> tm().put(bulkPricingPackage));
// Domains limit is 1, creating 2 domains to go over the limit
persistEppResource(
DatabaseHelper.newDomain("foo.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
persistEppResource(
DatabaseHelper.newDomain("buzz.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
action.run();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Found 1 packages over their active domains limit.");
.hasLogAtLevelWithMessage(
Level.INFO, "Found 1 bulk pricing packages over their active domains limit.");
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Package with package token abc123 has exceed their max active domains limit by 1"
+ " name(s).");
"Bulk pricing package with bulk token abc123 has exceed their max active domains limit"
+ " by 1 name(s).");
verify(emailService).sendEmail(emailCaptor.capture());
EmailMessage emailMessage = emailCaptor.getValue();
assertThat(emailMessage.subject()).isEqualTo(DOMAIN_LIMIT_WARNING_EMAIL_SUBJECT);
assertThat(emailMessage.body())
.isEqualTo(
String.format(DOMAIN_LIMIT_WARNING_EMAIL_BODY, 1, "abc123", "The Registrar", 1, 2));
PackagePromotion packageAfterCheck =
tm().transact(() -> PackagePromotion.loadByTokenString(token.getToken()).get());
BulkPricingPackage packageAfterCheck =
tm().transact(() -> BulkPricingPackage.loadByTokenString(token.getToken()).get());
assertThat(packageAfterCheck.getLastNotificationSent().get()).isEqualTo(clock.nowUtc());
}
@Test
void testSuccess_packageOverActiveDomainsLimitAlreadySentWarning30DaysAgo_SendsUpgradeEmail() {
packagePromotion =
packagePromotion
void
testSuccess_bulkPricingPackageOverActiveDomainsLimitAlreadySentWarning30DaysAgo_SendsUpgradeEmail() {
bulkPricingPackage =
bulkPricingPackage
.asBuilder()
.setMaxCreates(4)
.setMaxDomains(1)
.setLastNotificationSent(clock.nowUtc().minusDays(31))
.build();
tm().transact(() -> tm().put(packagePromotion));
tm().transact(() -> tm().put(bulkPricingPackage));
// Domains limit is 1, creating 2 domains to go over the limit
persistEppResource(
DatabaseHelper.newDomain("foo.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
persistEppResource(
DatabaseHelper.newDomain("buzz.tld", contact)
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
action.run();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Found 1 packages over their active domains limit.");
.hasLogAtLevelWithMessage(
Level.INFO, "Found 1 bulk pricing packages over their active domains limit.");
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Package with package token abc123 has exceed their max active domains limit by 1"
+ " name(s).");
"Bulk pricing package with bulk token abc123 has exceed their max active domains limit"
+ " by 1 name(s).");
verify(emailService).sendEmail(emailCaptor.capture());
EmailMessage emailMessage = emailCaptor.getValue();
assertThat(emailMessage.subject()).isEqualTo(DOMAIN_LIMIT_UPGRADE_EMAIL_SUBJECT);
assertThat(emailMessage.body())
.isEqualTo(
String.format(DOMAIN_LIMIT_UPGRADE_EMAIL_BODY, 1, "abc123", "The Registrar", 1, 2));
PackagePromotion packageAfterCheck =
tm().transact(() -> PackagePromotion.loadByTokenString(token.getToken()).get());
BulkPricingPackage packageAfterCheck =
tm().transact(() -> BulkPricingPackage.loadByTokenString(token.getToken()).get());
assertThat(packageAfterCheck.getLastNotificationSent().get()).isEqualTo(clock.nowUtc());
}
}

View File

@@ -35,6 +35,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableSet;
import google.registry.groups.GmailClient;
import google.registry.model.domain.Domain;
import google.registry.model.domain.RegistryLock;
import google.registry.model.host.Host;
@@ -48,7 +49,6 @@ import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.tools.DomainLockUtils;
import google.registry.util.EmailMessage;
import google.registry.util.SendEmailService;
import google.registry.util.StringGenerator.Alphabets;
import java.util.Optional;
import javax.mail.internet.InternetAddress;
@@ -85,7 +85,7 @@ public class RelockDomainActionTest {
private Domain domain;
private RegistryLock oldLock;
@Mock private SendEmailService sendEmailService;
@Mock private GmailClient gmailClient;
private RelockDomainAction action;
@BeforeEach
@@ -107,7 +107,7 @@ public class RelockDomainActionTest {
@AfterEach
void afterEach() {
verifyNoMoreInteractions(sendEmailService);
verifyNoMoreInteractions(gmailClient);
}
@Test
@@ -260,7 +260,7 @@ public class RelockDomainActionTest {
ImmutableSet.of(new InternetAddress("Marla.Singer.RegistryLock@crr.com")))
.setFrom(new InternetAddress("outgoing@example.com"))
.build();
verify(sendEmailService).sendEmail(expectedEmail);
verify(gmailClient).sendEmail(expectedEmail);
}
private void assertNonTransientFailureEmail(String exceptionMessage) throws Exception {
@@ -294,7 +294,7 @@ public class RelockDomainActionTest {
.setRecipients(recipients)
.setFrom(new InternetAddress("outgoing@example.com"))
.build();
verify(sendEmailService).sendEmail(expectedEmail);
verify(gmailClient).sendEmail(expectedEmail);
}
private void assertTaskEnqueued(int numAttempts) {
@@ -328,7 +328,7 @@ public class RelockDomainActionTest {
alertRecipientAddress,
gSuiteOutgoingAddress,
"support@example.com",
sendEmailService,
gmailClient,
domainLockUtils,
response);
}

View File

@@ -17,6 +17,7 @@ package google.registry.flows;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.TestDataHelper.loadFile;
import static google.registry.testing.TestLogHandlerUtils.findFirstLogMessageByPrefix;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@@ -30,11 +31,13 @@ import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.testing.TestLogHandler;
import google.registry.config.RegistryConfig;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.eppcommon.Trid;
import google.registry.model.eppoutput.EppOutput.ResponseOrGreeting;
import google.registry.model.eppoutput.EppResponse;
import google.registry.monitoring.whitebox.EppMetric;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.FakeClock;
@@ -46,7 +49,6 @@ import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Mockito;
/** Unit tests for {@link FlowRunner}. */
class FlowRunnerTest {
@@ -78,6 +80,21 @@ class FlowRunnerTest {
}
}
static class TestTransactionalFlow implements TransactionalFlow {
private final Optional<TransactionIsolationLevel> isolationLevel;
TestTransactionalFlow(Optional<TransactionIsolationLevel> isolationLevel) {
this.isolationLevel = isolationLevel;
}
@Override
public ResponseOrGreeting run() {
tm().assertTransactionIsolationLevel(
isolationLevel.orElse(tm().getDefaultTransactionIsolationLevel()));
return mock(EppResponse.class);
}
}
@BeforeEach
void beforeEach() {
JdkLoggerConfig.getConfig(FlowRunner.class).addHandler(handler);
@@ -90,23 +107,39 @@ class FlowRunnerTest {
flowRunner.isDryRun = false;
flowRunner.isSuperuser = false;
flowRunner.isTransactional = false;
flowRunner.isolationLevelOverride = Optional.empty();
flowRunner.sessionMetadata =
new StatelessRequestSessionMetadata("TheRegistrar", ImmutableSet.of());
flowRunner.trid = Trid.create("client-123", "server-456");
flowRunner.flowReporter = Mockito.mock(FlowReporter.class);
flowRunner.flowReporter = mock(FlowReporter.class);
}
@Test
void testRun_nonTransactionalCommand_setsCommandNameOnMetric() throws Exception {
flowRunner.isTransactional = true;
flowRunner.run(eppMetricBuilder);
assertThat(eppMetricBuilder.build().getCommandName()).hasValue("TestCommand");
}
@Test
void testRun_transactionalCommand_setsCommandNameOnMetric() throws Exception {
flowRunner.isTransactional = true;
flowRunner.flowClass = TestTransactionalFlow.class;
flowRunner.flowProvider = () -> new TestTransactionalFlow(Optional.empty());
flowRunner.run(eppMetricBuilder);
assertThat(eppMetricBuilder.build().getCommandName()).hasValue("TestCommand");
assertThat(eppMetricBuilder.build().getCommandName()).hasValue("TestTransactional");
}
@Test
void testRun_transactionalCommand_isolationLevelOverride() throws Exception {
flowRunner.isTransactional = true;
flowRunner.isolationLevelOverride =
Optional.of(TransactionIsolationLevel.TRANSACTION_READ_UNCOMMITTED);
flowRunner.flowClass = TestTransactionalFlow.class;
flowRunner.flowProvider = () -> new TestTransactionalFlow(flowRunner.isolationLevelOverride);
if (RegistryConfig.getHibernatePerTransactionIsolationEnabled()) {
flowRunner.run(eppMetricBuilder);
assertThat(eppMetricBuilder.build().getCommandName()).hasValue("TestTransactional");
}
}
@Test

View File

@@ -26,8 +26,8 @@ import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.DEF
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.NONPREMIUM;
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.domain.fee.Fee.FEE_EXTENSION_URIS;
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
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.eppcommon.StatusValue.PENDING_DELETE;
@@ -78,10 +78,10 @@ import google.registry.flows.FlowUtils.NotLoggedInException;
import google.registry.flows.FlowUtils.UnknownCurrencyEppException;
import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.domain.DomainCreateFlow.AnchorTenantCreatePeriodException;
import google.registry.flows.domain.DomainCreateFlow.BulkDomainRegisteredForTooManyYearsException;
import google.registry.flows.domain.DomainCreateFlow.MustHaveSignedMarksInCurrentPhaseException;
import google.registry.flows.domain.DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException;
import google.registry.flows.domain.DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException;
import google.registry.flows.domain.DomainCreateFlow.PackageDomainRegisteredForTooManyYearsException;
import google.registry.flows.domain.DomainCreateFlow.RenewalPriceInfo;
import google.registry.flows.domain.DomainCreateFlow.SignedMarksOnlyDuringSunriseException;
import google.registry.flows.domain.DomainFlowTmchUtils.FoundMarkExpiredException;
@@ -3673,12 +3673,12 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
}
@Test
void testSuccess_packageToken_addsTokenToDomain() throws Exception {
void testSuccess_bulkToken_addsTokenToDomain() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
@@ -3696,16 +3696,16 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.put("EXDATE", "2000-04-03T22:00:00.0Z")
.build()));
Domain domain = reloadResourceByForeignKey();
assertThat(domain.getCurrentPackageToken()).isPresent();
assertThat(domain.getCurrentPackageToken()).hasValue(token.createVKey());
assertThat(domain.getCurrentBulkToken()).isPresent();
assertThat(domain.getCurrentBulkToken()).hasValue(token.createVKey());
}
@Test
void testFailure_packageToken_registrationTooLong() throws Exception {
void testFailure_bulkToken_registrationTooLong() throws Exception {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
@@ -3715,10 +3715,10 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
EppException thrown =
assertThrows(PackageDomainRegisteredForTooManyYearsException.class, this::runFlow);
assertThrows(BulkDomainRegisteredForTooManyYearsException.class, this::runFlow);
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"The package token abc123 cannot be used to register names for longer than 1 year.");
"The bulk token abc123 cannot be used to register names for longer than 1 year.");
}
}

View File

@@ -1091,14 +1091,14 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setDiscountFraction(1)
.build());
domain = domain.asBuilder().setCurrentPackageToken(token.createVKey()).build();
domain = domain.asBuilder().setCurrentBulkToken(token.createVKey()).build();
persistResource(domain);
setEppInput("domain_info_bulk.xml");
doSuccessfulTest("domain_info_response_bulk.xml", false);
@@ -1111,14 +1111,14 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setDiscountFraction(1)
.build());
domain = domain.asBuilder().setCurrentPackageToken(token.createVKey()).build();
domain = domain.asBuilder().setCurrentBulkToken(token.createVKey()).build();
persistResource(domain);
sessionMetadata.setRegistrarId("TheRegistrar");
setEppInput("domain_info_bulk.xml");
@@ -1143,14 +1143,14 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setDiscountFraction(1)
.build());
domain = domain.asBuilder().setCurrentPackageToken(token.createVKey()).build();
domain = domain.asBuilder().setCurrentBulkToken(token.createVKey()).build();
persistResource(domain);
sessionMetadata.setRegistrarId("TheRegistrar");
setEppInput("domain_info_bulk.xml");

View File

@@ -20,8 +20,8 @@ import static google.registry.flows.domain.DomainTransferFlowTestCase.persistWit
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.DEFAULT;
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.NONPREMIUM;
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
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.persistence.transaction.TransactionManagerFactory.tm;
@@ -77,8 +77,8 @@ import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTok
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemoveDomainTokenOnPackageDomainException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.RemoveDomainTokenOnNonPackageDomainException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemoveDomainTokenOnBulkPricingDomainException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.RemoveDomainTokenOnNonBulkPricingDomainException;
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
import google.registry.model.billing.BillingBase.Flag;
import google.registry.model.billing.BillingBase.Reason;
@@ -1249,59 +1249,53 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
}
@Test
void testFailsPackageDomainInvalidAllocationToken() throws Exception {
void testFailsBulkPricingDomainInvalidAllocationToken() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
persistDomain();
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
reloadResourceByForeignKey().asBuilder().setCurrentBulkToken(token.createVKey()).build());
setEppInput(
"domain_renew_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "abc123"));
EppException thrown =
assertThrows(MissingRemoveDomainTokenOnPackageDomainException.class, this::runFlow);
assertThrows(MissingRemoveDomainTokenOnBulkPricingDomainException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailsToRenewPackageDomainNoRemoveDomainToken() throws Exception {
void testFailsToRenewBulkPricingDomainNoRemoveDomainToken() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
persistDomain();
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
reloadResourceByForeignKey().asBuilder().setCurrentBulkToken(token.createVKey()).build());
setEppInput("domain_renew.xml", ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "5"));
EppException thrown =
assertThrows(MissingRemoveDomainTokenOnPackageDomainException.class, this::runFlow);
assertThrows(MissingRemoveDomainTokenOnBulkPricingDomainException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailsToRenewNonPackageDomainWithRemoveDomainToken() throws Exception {
void testFailsToRenewNonBulkPricingDomainWithRemoveDomainToken() throws Exception {
persistDomain();
setEppInput(
@@ -1309,7 +1303,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "__REMOVEDOMAIN__"));
EppException thrown =
assertThrows(RemoveDomainTokenOnNonPackageDomainException.class, this::runFlow);
assertThrows(RemoveDomainTokenOnNonBulkPricingDomainException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@@ -1319,17 +1313,14 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
persistDomain(SPECIFIED, Money.of(USD, 2));
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
reloadResourceByForeignKey().asBuilder().setCurrentBulkToken(token.createVKey()).build());
setEppInput(
"domain_renew_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2", "TOKEN", "__REMOVEDOMAIN__"));
@@ -1339,10 +1330,10 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
2,
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2002-04-03T22:00:00Z"));
// We still need to verify that package token is removed as it's not being tested as a part of
// We still need to verify that the bulk token is removed as it's not being tested as a part of
// doSuccessfulTest
Domain domain = reloadResourceByForeignKey();
Truth8.assertThat(domain.getCurrentPackageToken()).isEmpty();
Truth8.assertThat(domain.getCurrentBulkToken()).isEmpty();
}
@Test
@@ -1351,17 +1342,14 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, Domain>
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
persistDomain(SPECIFIED, Money.of(USD, 2));
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
reloadResourceByForeignKey().asBuilder().setCurrentBulkToken(token.createVKey()).build());
setEppInput(
"domain_renew_allocationtoken.xml",

View File

@@ -17,7 +17,7 @@ package google.registry.flows.domain;
import static com.google.common.collect.MoreCollectors.onlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
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.reporting.DomainTransactionRecord.TransactionReportField.NET_ADDS_4_YR;
@@ -382,12 +382,12 @@ class DomainTransferApproveFlowTest
}
@Test
void testDryRun_PackageDomain() throws Exception {
void testDryRun_bulkPricingDomain() throws Exception {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.build());
@@ -398,8 +398,7 @@ class DomainTransferApproveFlowTest
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, new BigDecimal("10.00")))
.build());
persistResource(
domain.asBuilder().setCurrentPackageToken(allocationToken.createVKey()).build());
persistResource(domain.asBuilder().setCurrentBulkToken(allocationToken.createVKey()).build());
clock.advanceOneMilli();
setEppInput("domain_transfer_approve_wildcard.xml", ImmutableMap.of("DOMAIN", "example.tld"));
dryRunFlowAssertResponse(loadFile("domain_transfer_approve_response.xml"));
@@ -411,12 +410,12 @@ class DomainTransferApproveFlowTest
}
@Test
void testSuccess_removesPackageToken() throws Exception {
void testSuccess_removesBulkToken() throws Exception {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.build());
@@ -427,8 +426,7 @@ class DomainTransferApproveFlowTest
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, new BigDecimal("10.00")))
.build());
persistResource(
domain.asBuilder().setCurrentPackageToken(allocationToken.createVKey()).build());
persistResource(domain.asBuilder().setCurrentBulkToken(allocationToken.createVKey()).build());
clock.advanceOneMilli();
setEppInput("domain_transfer_approve_wildcard.xml", ImmutableMap.of("DOMAIN", "example.tld"));
DateTime now = clock.nowUtc();
@@ -460,7 +458,7 @@ class DomainTransferApproveFlowTest
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, new BigDecimal("10.00")))
.build());
assertThat(domain.getCurrentPackageToken()).isEmpty();
assertThat(domain.getCurrentBulkToken()).isEmpty();
assertThat(domain.getCurrentSponsorRegistrarId()).isEqualTo("NewRegistrar");
assertThat(loadByKey(domain.getAutorenewBillingEvent()).getRenewalPriceBehavior())
.isEqualTo(RenewalPriceBehavior.DEFAULT);

View File

@@ -21,7 +21,7 @@ import static com.google.common.truth.Truth8.assertThat;
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_REQUESTED_TIME;
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY;
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
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.reporting.DomainTransactionRecord.TransactionReportField.TRANSFER_SUCCESSFUL;
@@ -1329,7 +1329,7 @@ class DomainTransferRequestFlowTest
}
@Test
void testSuccess_specifiedRenewalPrice_notCarriedOverForPackageName() throws Exception {
void testSuccess_specifiedRenewalPrice_notCarriedOverForBulkPricingName() throws Exception {
setupDomain("example", "tld");
persistResource(Tld.get("tld").asBuilder().build());
domain = loadByEntity(domain);
@@ -1343,13 +1343,13 @@ class DomainTransferRequestFlowTest
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.build());
domain =
persistResource(
domain.asBuilder().setCurrentPackageToken(allocationToken.createVKey()).build());
domain.asBuilder().setCurrentBulkToken(allocationToken.createVKey()).build());
DateTime now = clock.nowUtc();
setEppInput("domain_transfer_request.xml");
@@ -1388,7 +1388,7 @@ class DomainTransferRequestFlowTest
}
@Test
void testSuccess_defaultRenewalPrice_carriedOverForPackageName() throws Exception {
void testSuccess_defaultRenewalPrice_carriedOverForBulkPricingName() throws Exception {
setupDomain("example", "tld");
persistResource(Tld.get("tld").asBuilder().build());
domain = loadByEntity(domain);
@@ -1401,13 +1401,13 @@ class DomainTransferRequestFlowTest
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.build());
domain =
persistResource(
domain.asBuilder().setCurrentPackageToken(allocationToken.createVKey()).build());
domain.asBuilder().setCurrentBulkToken(allocationToken.createVKey()).build());
DateTime now = clock.nowUtc();
setEppInput("domain_transfer_request.xml");
@@ -1445,7 +1445,7 @@ class DomainTransferRequestFlowTest
}
@Test
void testSuccess_packageName_zeroPeriod() throws Exception {
void testSuccess_bulkPricingName_zeroPeriod() throws Exception {
setupDomain("example", "tld");
persistResource(Tld.get("tld").asBuilder().build());
domain = loadByEntity(domain);
@@ -1458,13 +1458,13 @@ class DomainTransferRequestFlowTest
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.build());
domain =
persistResource(
domain.asBuilder().setCurrentPackageToken(allocationToken.createVKey()).build());
domain.asBuilder().setCurrentBulkToken(allocationToken.createVKey()).build());
doSuccessfulSuperuserExtensionTest(
"domain_transfer_request_superuser_extension.xml",

View File

@@ -17,7 +17,7 @@ package google.registry.model.domain;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT_STARTED;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.insertInDb;
@@ -131,7 +131,7 @@ public class DomainSqlTest {
allocationToken =
new AllocationToken.Builder()
.setToken("abc123Unlimited")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("dev", "app"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
@@ -152,9 +152,9 @@ public class DomainSqlTest {
}
@Test
void testDomainBasePersistenceWithCurrentPackageToken() {
void testDomainBasePersistenceWithCurrentBulkToken() {
persistResource(allocationToken);
domain = domain.asBuilder().setCurrentPackageToken(allocationToken.createVKey()).build();
domain = domain.asBuilder().setCurrentBulkToken(allocationToken.createVKey()).build();
persistDomain();
assertEqualDomainExcept(loadByKey(domain.createVKey()));
}

View File

@@ -20,7 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.testing.DatabaseHelper.cloneAndSetAutoTimestamps;
import static google.registry.testing.DatabaseHelper.createTld;
@@ -812,7 +812,7 @@ public class DomainTest {
}
@Test
void testClone_removesPackageFromTransferredDomain() {
void testClone_removesBulkTokenFromTransferredDomain() {
// If the transfer implicitly succeeded, the expiration time should be extended even if it
// hadn't already expired
DateTime now = DateTime.now(UTC);
@@ -831,7 +831,7 @@ public class DomainTest {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.build());
@@ -841,13 +841,13 @@ public class DomainTest {
.asBuilder()
.setRegistrationExpirationTime(previousExpiration)
.setTransferData(transferData)
.setCurrentPackageToken(allocationToken.createVKey())
.setCurrentBulkToken(allocationToken.createVKey())
.build());
assertThat(domain.getCurrentPackageToken()).isPresent();
assertThat(domain.getCurrentBulkToken()).isPresent();
Domain clonedDomain = domain.cloneProjectedAtTime(now);
assertThat(clonedDomain.getRegistrationExpirationTime()).isEqualTo(newExpiration);
assertThat(clonedDomain.getCurrentPackageToken()).isEmpty();
assertThat(clonedDomain.getCurrentBulkToken()).isEmpty();
}
@Test
@@ -876,7 +876,7 @@ public class DomainTest {
}
@Test
void testClone_doesNotRemovePackageForPendingTransfer() {
void testClone_doesNotRemoveBulkTokenForPendingTransfer() {
// Pending transfers shouldn't affect the expiration time
DateTime now = DateTime.now(UTC);
DateTime transferExpirationTime = now.plusDays(1);
@@ -892,7 +892,7 @@ public class DomainTest {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.build());
@@ -902,12 +902,12 @@ public class DomainTest {
.asBuilder()
.setRegistrationExpirationTime(previousExpiration)
.setTransferData(transferData)
.setCurrentPackageToken(allocationToken.createVKey())
.setCurrentBulkToken(allocationToken.createVKey())
.build());
Domain clonedDomain = domain.cloneProjectedAtTime(now);
assertThat(clonedDomain.getRegistrationExpirationTime()).isEqualTo(previousExpiration);
assertThat(clonedDomain.getCurrentPackageToken().get()).isEqualTo(allocationToken.createVKey());
assertThat(clonedDomain.getCurrentBulkToken().get()).isEqualTo(allocationToken.createVKey());
}
@Test
@@ -1043,48 +1043,48 @@ public class DomainTest {
}
@Test
void testFail_currentPackageTokenWrongPackageType() {
void testFail_currentBulkTokenWrongTokenType() {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder().setToken("abc123").setTokenType(SINGLE_USE).build());
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> domain.asBuilder().setCurrentPackageToken(allocationToken.createVKey()).build());
() -> domain.asBuilder().setCurrentBulkToken(allocationToken.createVKey()).build());
assertThat(thrown)
.hasMessageThat()
.isEqualTo("The currentPackageToken must have a PACKAGE TokenType");
.isEqualTo("The currentBulkToken must have a BULK_PRICING TokenType");
}
@Test
void testFailure_packageTokenDoesNotExist() {
void testFailure_bulkTokenDoesNotExist() {
AllocationToken allocationToken =
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.build();
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> domain.asBuilder().setCurrentPackageToken(allocationToken.createVKey()).build());
assertThat(thrown).hasMessageThat().isEqualTo("The package token abc123 does not exist");
() -> domain.asBuilder().setCurrentBulkToken(allocationToken.createVKey()).build());
assertThat(thrown).hasMessageThat().isEqualTo("The bulk token abc123 does not exist");
}
@Test
void testSuccess_removeCurrentPackageToken() {
void testSuccess_removeCurrentBulkToken() {
AllocationToken allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.build());
domain = domain.asBuilder().setCurrentPackageToken(allocationToken.createVKey()).build();
assertThat(domain.getCurrentPackageToken().get()).isEqualTo(allocationToken.createVKey());
domain = domain.asBuilder().setCurrentPackageToken(null).build();
assertThat(domain.getCurrentPackageToken()).isEmpty();
domain = domain.asBuilder().setCurrentBulkToken(allocationToken.createVKey()).build();
assertThat(domain.getCurrentBulkToken().get()).isEqualTo(allocationToken.createVKey());
domain = domain.asBuilder().setCurrentBulkToken(null).build();
assertThat(domain.getCurrentBulkToken()).isEmpty();
}
}

View File

@@ -20,7 +20,7 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.CAN
import static google.registry.model.domain.token.AllocationToken.TokenStatus.ENDED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT_STARTED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
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.testing.DatabaseHelper.createTld;
@@ -218,28 +218,28 @@ public class AllocationTokenTest extends EntityTestCase {
}
@Test
void testFail_packageTokenNotSpecifiedRenewalBehavior() {
void testFail_bulkTokenNotSpecifiedRenewalBehavior() {
AllocationToken.Builder builder =
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT);
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, builder::build);
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Package tokens must have renewalPriceBehavior set to SPECIFIED");
.isEqualTo("Bulk tokens must have renewalPriceBehavior set to SPECIFIED");
}
@Test
void testFail_packageTokenDiscountPremium() {
void testFail_bulkTokenDiscountPremium() {
AllocationToken.Builder builder =
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setDiscountPremiums(true);
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, builder::build);
assertThat(thrown).hasMessageThat().isEqualTo("Package tokens cannot discount premium names");
assertThat(thrown).hasMessageThat().isEqualTo("Bulk tokens cannot discount premium names");
}
@Test
@@ -292,17 +292,17 @@ public class AllocationTokenTest extends EntityTestCase {
}
@Test
void testBuild_onlyOneClientInPackage() {
void testBuild_onlyOneClientInBulkPricingPackage() {
Buildable.Builder<AllocationToken> builder =
new AllocationToken.Builder()
.setToken("foobar")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("foo", "bar"));
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, builder::build);
assertThat(thrown)
.hasMessageThat()
.isEqualTo("PACKAGE tokens must have exactly one allowed client registrar");
.isEqualTo("BULK_PRICING tokens must have exactly one allowed client registrar");
}
@Test

View File

@@ -31,10 +31,10 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
/** Unit tests for {@link PackagePromotion}. */
public class PackagePromotionTest extends EntityTestCase {
/** Unit tests for {@link BulkPricingPackage}. */
public class BulkPricingPackageTest extends EntityTestCase {
public PackagePromotionTest() {
public BulkPricingPackageTest() {
super(JpaEntityCoverageCheck.ENABLED);
}
@@ -49,7 +49,7 @@ public class PackagePromotionTest extends EntityTestCase {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
@@ -57,23 +57,23 @@ public class PackagePromotionTest extends EntityTestCase {
.setDiscountFraction(1)
.build());
PackagePromotion packagePromotion =
new PackagePromotion.Builder()
BulkPricingPackage bulkPricingPackage =
new BulkPricingPackage.Builder()
.setToken(token)
.setPackagePrice(Money.of(CurrencyUnit.USD, 10000))
.setBulkPrice(Money.of(CurrencyUnit.USD, 10000))
.setMaxCreates(40)
.setMaxDomains(10)
.setNextBillingDate(DateTime.parse("2011-11-12T05:00:00Z"))
.build();
tm().transact(() -> tm().put(packagePromotion));
tm().transact(() -> tm().put(bulkPricingPackage));
assertAboutImmutableObjects()
.that(tm().transact(() -> PackagePromotion.loadByTokenString("abc123")).get())
.isEqualExceptFields(packagePromotion, "packagePromotionId");
.that(tm().transact(() -> BulkPricingPackage.loadByTokenString("abc123")).get())
.isEqualExceptFields(bulkPricingPackage, "bulkPricingId");
}
@Test
void testFail_tokenIsNotPackage() {
void testFail_tokenIsNotBulkToken() {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
@@ -90,14 +90,14 @@ public class PackagePromotionTest extends EntityTestCase {
IllegalArgumentException.class,
() ->
persistResource(
new PackagePromotion.Builder()
new BulkPricingPackage.Builder()
.setToken(token)
.setPackagePrice(Money.of(CurrencyUnit.USD, 10000))
.setBulkPrice(Money.of(CurrencyUnit.USD, 10000))
.setMaxCreates(40)
.setMaxDomains(10)
.setNextBillingDate(DateTime.parse("2011-11-12T05:00:00Z"))
.build()));
assertThat(thrown).hasMessageThat().isEqualTo("Allocation token must be a PACKAGE type");
assertThat(thrown).hasMessageThat().isEqualTo("Allocation token must be a BULK_PRICING type");
}
}

View File

@@ -507,11 +507,14 @@ class RegistrarTest extends EntityTestCase {
@Test
void testSuccess_setAllowedTldsUncached() {
assertThat(
registrar
.asBuilder()
.setAllowedTldsUncached(ImmutableSet.of("xn--q9jyb4c"))
.build()
.getAllowedTlds())
tm().transact(
() -> {
return registrar
.asBuilder()
.setAllowedTldsUncached(ImmutableSet.of("xn--q9jyb4c"))
.build()
.getAllowedTlds();
}))
.containsExactly("xn--q9jyb4c");
}
@@ -524,9 +527,12 @@ class RegistrarTest extends EntityTestCase {
@Test
void testFailure_setAllowedTldsUncached_nonexistentTld() {
assertThrows(
IllegalArgumentException.class,
() -> registrar.asBuilder().setAllowedTldsUncached(ImmutableSet.of("bad")));
tm().transact(
() -> {
assertThrows(
IllegalArgumentException.class,
() -> registrar.asBuilder().setAllowedTldsUncached(ImmutableSet.of("bad")));
});
}
@Test
@@ -614,11 +620,14 @@ class RegistrarTest extends EntityTestCase {
// Test that the uncached version works
assertThat(
registrar
.asBuilder()
.setAllowedTldsUncached(ImmutableSet.of("newtld"))
.build()
.getAllowedTlds())
tm().transact(
() -> {
return registrar
.asBuilder()
.setAllowedTldsUncached(ImmutableSet.of("newtld"))
.build()
.getAllowedTlds();
}))
.containsExactly("newtld");
// Test that the "regular" cached version fails. If this doesn't throw - then we changed how

View File

@@ -16,6 +16,9 @@ package google.registry.persistence.transaction;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_READ_COMMITTED;
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_READ_UNCOMMITTED;
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.assertDetachedFromEntityManager;
import static google.registry.testing.DatabaseHelper.existsInDb;
@@ -31,6 +34,7 @@ import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.config.RegistryConfig;
import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestExtensions.JpaUnitTestExtension;
@@ -81,7 +85,7 @@ class JpaTransactionManagerImplTest {
.buildUnitTestExtension();
@Test
void transact_succeeds() {
void transact_success() {
assertPersonEmpty();
assertCompanyEmpty();
tm().transact(
@@ -89,6 +93,7 @@ class JpaTransactionManagerImplTest {
insertPerson(10);
insertCompany("Foo");
insertCompany("Bar");
tm().assertTransactionIsolationLevel(tm().getDefaultTransactionIsolationLevel());
});
assertPersonCount(1);
assertPersonExist(10);
@@ -97,6 +102,149 @@ class JpaTransactionManagerImplTest {
assertCompanyExist("Bar");
}
@Test
void transact_setIsolationLevel() {
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(
RegistryConfig.getHibernatePerTransactionIsolationEnabled()
? TRANSACTION_READ_UNCOMMITTED
: tm().getDefaultTransactionIsolationLevel());
return null;
},
TRANSACTION_READ_UNCOMMITTED);
// Make sure that we can start a new transaction on the same thread with a different isolation
// level.
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(
RegistryConfig.getHibernatePerTransactionIsolationEnabled()
? TRANSACTION_REPEATABLE_READ
: tm().getDefaultTransactionIsolationLevel());
return null;
},
TRANSACTION_REPEATABLE_READ);
}
@Test
void transact_nestedTransactions_perTransactionIsolationLevelEnabled() {
if (!RegistryConfig.getHibernatePerTransactionIsolationEnabled()) {
return;
}
// Nested transactions allowed (both at the default isolation level).
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(tm().getDefaultTransactionIsolationLevel());
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(
tm().getDefaultTransactionIsolationLevel());
});
});
// Nested transactions allowed (enclosed transaction does not have an override, using the
// enclosing transaction's level).
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(TRANSACTION_READ_UNCOMMITTED);
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(TRANSACTION_READ_UNCOMMITTED);
});
},
TRANSACTION_READ_UNCOMMITTED);
// Nested transactions allowed (Both have the same override).
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(TRANSACTION_REPEATABLE_READ);
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(TRANSACTION_REPEATABLE_READ);
},
TRANSACTION_REPEATABLE_READ);
},
TRANSACTION_REPEATABLE_READ);
// Nested transactions disallowed (enclosed transaction has an override that conflicts from the
// default).
IllegalStateException e =
assertThrows(
IllegalStateException.class,
() ->
tm().transact(
() -> {
tm().transact(() -> {}, TRANSACTION_READ_COMMITTED);
}));
assertThat(e).hasMessageThat().contains("conflict detected");
// Nested transactions disallowed (conflicting overrides).
e =
assertThrows(
IllegalStateException.class,
() ->
tm().transact(
() -> {
tm().transact(() -> {}, TRANSACTION_READ_COMMITTED);
},
TRANSACTION_REPEATABLE_READ));
assertThat(e).hasMessageThat().contains("conflict detected");
}
@Test
void transact_nestedTransactions_perTransactionIsolationLevelDisabled() {
if (RegistryConfig.getHibernatePerTransactionIsolationEnabled()) {
return;
}
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(tm().getDefaultTransactionIsolationLevel());
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(
tm().getDefaultTransactionIsolationLevel());
});
});
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(tm().getDefaultTransactionIsolationLevel());
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(
tm().getDefaultTransactionIsolationLevel());
});
},
TRANSACTION_READ_UNCOMMITTED);
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(tm().getDefaultTransactionIsolationLevel());
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(
tm().getDefaultTransactionIsolationLevel());
},
TRANSACTION_READ_UNCOMMITTED);
});
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(tm().getDefaultTransactionIsolationLevel());
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(
tm().getDefaultTransactionIsolationLevel());
},
TRANSACTION_READ_UNCOMMITTED);
},
TRANSACTION_READ_UNCOMMITTED);
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(tm().getDefaultTransactionIsolationLevel());
tm().transact(
() -> {
tm().assertTransactionIsolationLevel(
tm().getDefaultTransactionIsolationLevel());
},
TRANSACTION_READ_COMMITTED);
},
TRANSACTION_READ_UNCOMMITTED);
}
@Test
void transact_hasNoEffectWithPartialSuccess() {
assertPersonEmpty();
@@ -606,19 +754,19 @@ class JpaTransactionManagerImplTest {
verify(spyJpaTm, times(3)).delete(theEntityKey);
}
private void insertPerson(int age) {
private static void insertPerson(int age) {
tm().getEntityManager()
.createNativeQuery(String.format("INSERT INTO Person (age) VALUES (%d)", age))
.executeUpdate();
}
private void insertCompany(String name) {
private static void insertCompany(String name) {
tm().getEntityManager()
.createNativeQuery(String.format("INSERT INTO Company (name) VALUES ('%s')", name))
.executeUpdate();
}
private void assertPersonExist(int age) {
private static void assertPersonExist(int age) {
tm().transact(
() -> {
EntityManager em = tm().getEntityManager();
@@ -631,7 +779,7 @@ class JpaTransactionManagerImplTest {
});
}
private void assertCompanyExist(String name) {
private static void assertCompanyExist(String name) {
tm().transact(
() -> {
String maybeName =
@@ -644,23 +792,23 @@ class JpaTransactionManagerImplTest {
});
}
private void assertPersonCount(int count) {
private static void assertPersonCount(int count) {
assertThat(countTable("Person")).isEqualTo(count);
}
private void assertCompanyCount(int count) {
private static void assertCompanyCount(int count) {
assertThat(countTable("Company")).isEqualTo(count);
}
private void assertPersonEmpty() {
private static void assertPersonEmpty() {
assertPersonCount(0);
}
private void assertCompanyEmpty() {
private static void assertCompanyEmpty() {
assertCompanyCount(0);
}
private int countTable(String tableName) {
private static int countTable(String tableName) {
return tm().transact(
() -> {
BigInteger colCount =

View File

@@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.model.ImmutableObject;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.VKey;
import java.util.Optional;
import java.util.function.Supplier;
@@ -49,6 +50,21 @@ public class ReplicaSimulatingJpaTransactionManager implements JpaTransactionMan
delegate.teardown();
}
@Override
public TransactionIsolationLevel getDefaultTransactionIsolationLevel() {
return delegate.getDefaultTransactionIsolationLevel();
}
@Override
public TransactionIsolationLevel getCurrentTransactionIsolationLevel() {
return delegate.getCurrentTransactionIsolationLevel();
}
@Override
public void assertTransactionIsolationLevel(TransactionIsolationLevel expectedLevel) {
delegate.assertTransactionIsolationLevel(expectedLevel);
}
@Override
public EntityManager getStandaloneEntityManager() {
return delegate.getStandaloneEntityManager();
@@ -85,7 +101,7 @@ public class ReplicaSimulatingJpaTransactionManager implements JpaTransactionMan
}
@Override
public <T> T transact(Supplier<T> work) {
public <T> T transact(Supplier<T> work, TransactionIsolationLevel isolationLevel) {
if (delegate.inTransaction()) {
return work.get();
}
@@ -96,26 +112,48 @@ public class ReplicaSimulatingJpaTransactionManager implements JpaTransactionMan
.createNativeQuery("SET TRANSACTION READ ONLY")
.executeUpdate();
return work.get();
});
},
isolationLevel);
}
@Override
public <T> T transact(Supplier<T> work) {
return transact(work, null);
}
@Override
public <T> T transactNoRetry(Supplier<T> work, TransactionIsolationLevel isolationLevel) {
return transact(work, isolationLevel);
}
@Override
public <T> T transactNoRetry(Supplier<T> work) {
return transact(work);
return transactNoRetry(work, null);
}
@Override
public void transact(Runnable work) {
public void transact(Runnable work, TransactionIsolationLevel isolationLevel) {
transact(
() -> {
work.run();
return null;
});
},
isolationLevel);
}
@Override
public void transact(Runnable work) {
transact(work, null);
}
@Override
public void transactNoRetry(Runnable work, TransactionIsolationLevel isolationLevel) {
transact(work, isolationLevel);
}
@Override
public void transactNoRetry(Runnable work) {
transact(work);
transactNoRetry(work, null);
}
@Override

View File

@@ -16,9 +16,9 @@ package google.registry.reporting.billing;
import static com.google.common.truth.Truth.assertThat;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -101,7 +101,7 @@ class PublishInvoicesActionTest {
void testJobIndeterminate_returnsRetriableResponse() {
expectedJob.setCurrentState("JOB_STATE_RUNNING");
uploadAction.run();
assertThat(response.getStatus()).isEqualTo(SC_NOT_MODIFIED);
assertThat(response.getStatus()).isEqualTo(SC_SERVICE_UNAVAILABLE);
}
@Test

View File

@@ -25,6 +25,7 @@ import com.google.cloud.tasks.v2.HttpMethod;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.bigquery.BigqueryJobFailureException;
import google.registry.groups.GmailClient;
import google.registry.reporting.icann.IcannReportingModule.ReportType;
import google.registry.request.HttpException.BadRequestException;
import google.registry.testing.CloudTasksHelper;
@@ -34,7 +35,6 @@ import google.registry.testing.FakeResponse;
import google.registry.testing.FakeSleeper;
import google.registry.util.EmailMessage;
import google.registry.util.Retrier;
import google.registry.util.SendEmailService;
import java.util.Optional;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
@@ -65,7 +65,7 @@ class IcannReportingStagingActionTest {
action.retrier = new Retrier(new FakeSleeper(new FakeClock()), 3);
action.sender = new InternetAddress("sender@example.com");
action.recipient = new InternetAddress("recipient@example.com");
action.emailService = mock(SendEmailService.class);
action.gmailClient = mock(GmailClient.class);
action.cloudTasksUtils = cloudTasksHelper.getTestCloudTasksUtils();
when(stager.stageReports(yearMonth, subdir, ReportType.ACTIVITY))
@@ -89,7 +89,7 @@ class IcannReportingStagingActionTest {
action.run();
verify(stager).stageReports(yearMonth, subdir, ReportType.ACTIVITY);
verify(stager).createAndUploadManifest(subdir, ImmutableList.of("a", "b"));
verify(action.emailService)
verify(action.gmailClient)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report staging summary [SUCCESS]",
@@ -105,7 +105,7 @@ class IcannReportingStagingActionTest {
verify(stager).stageReports(yearMonth, subdir, ReportType.ACTIVITY);
verify(stager).stageReports(yearMonth, subdir, ReportType.TRANSACTIONS);
verify(stager).createAndUploadManifest(subdir, ImmutableList.of("a", "b", "c", "d"));
verify(action.emailService)
verify(action.gmailClient)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report staging summary [SUCCESS]",
@@ -124,7 +124,7 @@ class IcannReportingStagingActionTest {
verify(stager, times(2)).stageReports(yearMonth, subdir, ReportType.ACTIVITY);
verify(stager, times(2)).stageReports(yearMonth, subdir, ReportType.TRANSACTIONS);
verify(stager).createAndUploadManifest(subdir, ImmutableList.of("a", "b", "c", "d"));
verify(action.emailService)
verify(action.gmailClient)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report staging summary [SUCCESS]",
@@ -147,7 +147,7 @@ class IcannReportingStagingActionTest {
.hasMessageThat()
.isEqualTo("BigqueryJobFailureException: Expected failure");
verify(stager, times(3)).stageReports(yearMonth, subdir, ReportType.ACTIVITY);
verify(action.emailService)
verify(action.gmailClient)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report staging summary [FAILURE]",

View File

@@ -31,6 +31,7 @@ import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
import com.google.common.testing.TestLogHandler;
import google.registry.gcs.GcsUtils;
import google.registry.groups.GmailClient;
import google.registry.model.common.Cursor;
import google.registry.model.common.Cursor.CursorType;
import google.registry.model.tld.Tld;
@@ -43,7 +44,6 @@ import google.registry.testing.FakeResponse;
import google.registry.testing.FakeSleeper;
import google.registry.util.EmailMessage;
import google.registry.util.Retrier;
import google.registry.util.SendEmailService;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -63,7 +63,7 @@ class IcannReportingUploadActionTest {
private static final byte[] PAYLOAD_SUCCESS = "test,csv\n13,37".getBytes(UTF_8);
private static final byte[] PAYLOAD_FAIL = "ahah,csv\n12,34".getBytes(UTF_8);
private final IcannHttpReporter mockReporter = mock(IcannHttpReporter.class);
private final SendEmailService emailService = mock(SendEmailService.class);
private final GmailClient gmailClient = mock(GmailClient.class);
private final FakeResponse response = new FakeResponse();
private final GcsUtils gcsUtils = new GcsUtils(LocalStorageHelper.getOptions());
private final TestLogHandler logHandler = new TestLogHandler();
@@ -77,7 +77,7 @@ class IcannReportingUploadActionTest {
action.gcsUtils = gcsUtils;
action.retrier = new Retrier(new FakeSleeper(new FakeClock()), 3);
action.reportingBucket = "basin";
action.emailService = emailService;
action.gmailClient = gmailClient;
action.sender = new InternetAddress("sender@example.com");
action.recipient = new InternetAddress("recipient@example.com");
action.response = response;
@@ -127,7 +127,7 @@ class IcannReportingUploadActionTest {
verify(mockReporter).send(PAYLOAD_SUCCESS, "tld-transactions-200606.csv");
verifyNoMoreInteractions(mockReporter);
verify(emailService)
verify(gmailClient)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 3/4 succeeded",
@@ -162,7 +162,7 @@ class IcannReportingUploadActionTest {
verify(mockReporter).send(PAYLOAD_SUCCESS, "tld-transactions-200512.csv");
verifyNoMoreInteractions(mockReporter);
verify(emailService)
verify(gmailClient)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 2/2 succeeded",
@@ -191,7 +191,7 @@ class IcannReportingUploadActionTest {
IcannReportingUploadAction action = createAction();
action.run();
verifyNoMoreInteractions(mockReporter);
verifyNoMoreInteractions(emailService);
verifyNoMoreInteractions(gmailClient);
}
@Test
@@ -206,7 +206,7 @@ class IcannReportingUploadActionTest {
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-transactions-200606.csv");
verify(mockReporter, times(2)).send(PAYLOAD_SUCCESS, "tld-transactions-200606.csv");
verifyNoMoreInteractions(mockReporter);
verify(emailService)
verify(gmailClient)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 3/4 succeeded",
@@ -266,7 +266,7 @@ class IcannReportingUploadActionTest {
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-transactions-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "tld-transactions-200606.csv");
verifyNoMoreInteractions(mockReporter);
verify(emailService)
verify(gmailClient)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 3/4 succeeded",
@@ -336,7 +336,7 @@ class IcannReportingUploadActionTest {
verify(mockReporter).send(PAYLOAD_SUCCESS, "tld-transactions-200606.csv");
verifyNoMoreInteractions(mockReporter);
verify(emailService)
verify(gmailClient)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 3/4 succeeded",

View File

@@ -93,8 +93,7 @@ class GenerateSpec11ReportActionTest extends BeamActionTestBase {
.scheduleTime(
clock
.nowUtc()
// TODO(b/296582836): mitigating retry problem. Remove `+10` when bug is fixed.
.plus(Duration.standardMinutes(ReportingModule.ENQUEUE_DELAY_MINUTES + 10))));
.plus(Duration.standardMinutes(ReportingModule.ENQUEUE_DELAY_MINUTES))));
}
@Test

View File

@@ -19,9 +19,9 @@ import static google.registry.reporting.spec11.Spec11RegistrarThreatMatchesParse
import static google.registry.reporting.spec11.Spec11RegistrarThreatMatchesParserTest.getMatchB;
import static google.registry.reporting.spec11.Spec11RegistrarThreatMatchesParserTest.sampleThreatMatches;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -134,7 +134,7 @@ class PublishSpec11ReportActionTest {
void testJobIndeterminate_returnsRetriableResponse() {
expectedJob.setCurrentState("JOB_STATE_RUNNING");
publishAction.run();
assertThat(response.getStatus()).isEqualTo(SC_NOT_MODIFIED);
assertThat(response.getStatus()).isEqualTo(SC_SERVICE_UNAVAILABLE);
verifyNoMoreInteractions(emailUtils);
}

View File

@@ -23,7 +23,7 @@ import google.registry.model.console.UserTest;
import google.registry.model.contact.ContactTest;
import google.registry.model.domain.DomainSqlTest;
import google.registry.model.domain.token.AllocationTokenTest;
import google.registry.model.domain.token.PackagePromotionTest;
import google.registry.model.domain.token.BulkPricingPackageTest;
import google.registry.model.history.ContactHistoryTest;
import google.registry.model.history.DomainHistoryTest;
import google.registry.model.history.HostHistoryTest;
@@ -82,6 +82,7 @@ import org.junit.runner.RunWith;
BeforeSuiteTest.class,
AllocationTokenTest.class,
BillingBaseTest.class,
BulkPricingPackageTest.class,
ClaimsListDaoTest.class,
ContactHistoryTest.class,
ContactTest.class,
@@ -91,7 +92,6 @@ import org.junit.runner.RunWith;
DomainHistoryTest.class,
HostHistoryTest.class,
LockTest.class,
PackagePromotionTest.class,
PollMessageTest.class,
PremiumListDaoTest.class,
RdeRevisionTest.class,

View File

@@ -24,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.PackagePromotion;
import google.registry.model.domain.token.BulkPricingPackage;
import java.util.Optional;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
@@ -32,16 +32,16 @@ import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
/** Unit tests for {@link google.registry.tools.CreatePackagePromotionCommand}. */
public class CreatePackagePromotionCommandTest
extends CommandTestCase<CreatePackagePromotionCommand> {
/** Unit tests for {@link CreateBulkPricingPackageCommand}. */
public class CreateBulkPricingPackageCommandTest
extends CommandTestCase<CreateBulkPricingPackageCommand> {
@Test
void testSuccess() throws Exception {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
@@ -55,20 +55,20 @@ public class CreatePackagePromotionCommandTest
"--next_billing_date=2012-03-17",
"abc123");
Optional<PackagePromotion> packagePromotionOptional =
tm().transact(() -> PackagePromotion.loadByTokenString("abc123"));
assertThat(packagePromotionOptional).isPresent();
PackagePromotion packagePromotion = packagePromotionOptional.get();
assertThat(packagePromotion.getMaxDomains()).isEqualTo(100);
assertThat(packagePromotion.getMaxCreates()).isEqualTo(500);
assertThat(packagePromotion.getPackagePrice()).isEqualTo(Money.of(CurrencyUnit.USD, 1000));
assertThat(packagePromotion.getNextBillingDate())
Optional<BulkPricingPackage> bulkPricingPackageOptional =
tm().transact(() -> BulkPricingPackage.loadByTokenString("abc123"));
assertThat(bulkPricingPackageOptional).isPresent();
BulkPricingPackage bulkPricingPackage = bulkPricingPackageOptional.get();
assertThat(bulkPricingPackage.getMaxDomains()).isEqualTo(100);
assertThat(bulkPricingPackage.getMaxCreates()).isEqualTo(500);
assertThat(bulkPricingPackage.getBulkPrice()).isEqualTo(Money.of(CurrencyUnit.USD, 1000));
assertThat(bulkPricingPackage.getNextBillingDate())
.isEqualTo(DateTime.parse("2012-03-17T00:00:00Z"));
assertThat(packagePromotion.getLastNotificationSent()).isEmpty();
assertThat(bulkPricingPackage.getLastNotificationSent()).isEmpty();
}
@Test
void testFailure_tokenIsNotPackageType() throws Exception {
void testFailure_tokenIsNotBulkType() throws Exception {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
@@ -90,7 +90,7 @@ public class CreatePackagePromotionCommandTest
"--next_billing_date=2012-03-17T05:00:00Z",
"abc123"));
assertThat(thrown.getMessage())
.isEqualTo("The allocation token must be of the PACKAGE token type");
.isEqualTo("The allocation token must be of the BULK_PRICING token type");
}
@Test
@@ -107,16 +107,16 @@ public class CreatePackagePromotionCommandTest
"abc123"));
assertThat(thrown.getMessage())
.isEqualTo(
"An allocation token with the token String abc123 does not exist. The package token"
+ " must be created first before it can be used to create a PackagePromotion");
"An allocation token with the token String abc123 does not exist. The bulk token"
+ " must be created first before it can be used to create a BulkPricingPackage");
}
@Test
void testFailure_packagePromotionAlreadyExists() throws Exception {
void testFailure_bulkPricingPackageAlreadyExists() throws Exception {
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
@@ -129,9 +129,9 @@ public class CreatePackagePromotionCommandTest
"--price=USD 1000.00",
"--next_billing_date=2012-03-17T05:00:00Z",
"abc123");
Optional<PackagePromotion> packagePromotionOptional =
tm().transact(() -> PackagePromotion.loadByTokenString("abc123"));
assertThat(packagePromotionOptional).isPresent();
Optional<BulkPricingPackage> bulkPricingPackageOptional =
tm().transact(() -> BulkPricingPackage.loadByTokenString("abc123"));
assertThat(bulkPricingPackageOptional).isPresent();
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
@@ -142,7 +142,8 @@ public class CreatePackagePromotionCommandTest
"--price=USD 1000.00",
"--next_billing_date=2012-03-17T05:00:00Z",
"abc123"));
assertThat(thrown.getMessage()).isEqualTo("PackagePromotion with token abc123 already exists");
assertThat(thrown.getMessage())
.isEqualTo("BulkPricingPackage with token abc123 already exists");
}
@Test
@@ -150,7 +151,7 @@ public class CreatePackagePromotionCommandTest
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
@@ -158,16 +159,16 @@ public class CreatePackagePromotionCommandTest
.setDiscountFraction(1)
.build());
runCommandForced("--price=USD 1000.00", "--next_billing_date=2012-03-17", "abc123");
Optional<PackagePromotion> packagePromotionOptional =
tm().transact(() -> PackagePromotion.loadByTokenString("abc123"));
assertThat(packagePromotionOptional).isPresent();
PackagePromotion packagePromotion = packagePromotionOptional.get();
assertThat(packagePromotion.getMaxDomains()).isEqualTo(0);
assertThat(packagePromotion.getMaxCreates()).isEqualTo(0);
assertThat(packagePromotion.getPackagePrice()).isEqualTo(Money.of(CurrencyUnit.USD, 1000));
assertThat(packagePromotion.getNextBillingDate())
Optional<BulkPricingPackage> bulkPricingPackageOptional =
tm().transact(() -> BulkPricingPackage.loadByTokenString("abc123"));
assertThat(bulkPricingPackageOptional).isPresent();
BulkPricingPackage bulkPricingPackage = bulkPricingPackageOptional.get();
assertThat(bulkPricingPackage.getMaxDomains()).isEqualTo(0);
assertThat(bulkPricingPackage.getMaxCreates()).isEqualTo(0);
assertThat(bulkPricingPackage.getBulkPrice()).isEqualTo(Money.of(CurrencyUnit.USD, 1000));
assertThat(bulkPricingPackage.getNextBillingDate())
.isEqualTo(DateTime.parse("2012-03-17T00:00:00Z"));
assertThat(packagePromotion.getLastNotificationSent()).isEmpty();
assertThat(bulkPricingPackage.getLastNotificationSent()).isEmpty();
}
@Test
@@ -175,7 +176,7 @@ public class CreatePackagePromotionCommandTest
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
@@ -184,15 +185,15 @@ public class CreatePackagePromotionCommandTest
.build());
runCommandForced("--max_domains=100", "--max_creates=500", "--price=USD 1000.00", "abc123");
Optional<PackagePromotion> packagePromotionOptional =
tm().transact(() -> PackagePromotion.loadByTokenString("abc123"));
assertThat(packagePromotionOptional).isPresent();
PackagePromotion packagePromotion = packagePromotionOptional.get();
assertThat(packagePromotion.getMaxDomains()).isEqualTo(100);
assertThat(packagePromotion.getMaxCreates()).isEqualTo(500);
assertThat(packagePromotion.getPackagePrice()).isEqualTo(Money.of(CurrencyUnit.USD, 1000));
assertThat(packagePromotion.getNextBillingDate()).isEqualTo(END_OF_TIME);
assertThat(packagePromotion.getLastNotificationSent()).isEmpty();
Optional<BulkPricingPackage> bulkPricingPackageOptional =
tm().transact(() -> BulkPricingPackage.loadByTokenString("abc123"));
assertThat(bulkPricingPackageOptional).isPresent();
BulkPricingPackage bulkPricingPackage = bulkPricingPackageOptional.get();
assertThat(bulkPricingPackage.getMaxDomains()).isEqualTo(100);
assertThat(bulkPricingPackage.getMaxCreates()).isEqualTo(500);
assertThat(bulkPricingPackage.getBulkPrice()).isEqualTo(Money.of(CurrencyUnit.USD, 1000));
assertThat(bulkPricingPackage.getNextBillingDate()).isEqualTo(END_OF_TIME);
assertThat(bulkPricingPackage.getLastNotificationSent()).isEmpty();
}
@Test
@@ -200,7 +201,7 @@ public class CreatePackagePromotionCommandTest
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
@@ -217,6 +218,6 @@ public class CreatePackagePromotionCommandTest
"--next_billing_date=2012-03-17T05:00:00Z",
"abc123"));
assertThat(thrown.getMessage())
.isEqualTo("PackagePrice is required when creating a new package");
.isEqualTo("BulkPrice is required when creating a new bulk pricing package");
}
}

View File

@@ -379,8 +379,8 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Invalid value for -t parameter. Allowed values:[DEFAULT_PROMO, PACKAGE, SINGLE_USE,"
+ " UNLIMITED_USE]");
"Invalid value for -t parameter. Allowed values:[BULK_PRICING, DEFAULT_PROMO, PACKAGE,"
+ " SINGLE_USE, UNLIMITED_USE]");
}
@Test
@@ -399,7 +399,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
}
@Test
void testFailure_invalidPackageTokenStatusTransition() {
void testFailure_invalidBulkTokenStatusTransition() {
assertThat(
assertThrows(
IllegalArgumentException.class,
@@ -408,14 +408,14 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
"--number",
"999",
"--type",
"PACKAGE",
"BULK_PRICING",
String.format(
"--token_status_transitions=\"%s=NOT_STARTED,%s=VALID,%s=ENDED\"",
START_OF_TIME, fakeClock.nowUtc(), fakeClock.nowUtc().plusDays(1)))))
.hasMessageThat()
.isEqualTo(
"PACKAGE tokens should not be generated with ENDED or CANCELLED in their transition"
+ " map");
"BULK_PRICING tokens should not be generated with ENDED or CANCELLED in their"
+ " transition map");
}
@Test

View File

@@ -24,15 +24,16 @@ import com.google.common.collect.ImmutableSet;
import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.PackagePromotion;
import google.registry.model.domain.token.BulkPricingPackage;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link GetPackagePromotionCommand}. */
public class GetPackagePromotionCommandTest extends CommandTestCase<GetPackagePromotionCommand> {
/** Unit tests for {@link GetBulkPricingPackageCommand}. */
public class GetBulkPricingPackageCommandTest
extends CommandTestCase<GetBulkPricingPackageCommand> {
@BeforeEach
void beforeEach() {
@@ -45,33 +46,33 @@ public class GetPackagePromotionCommandTest extends CommandTestCase<GetPackagePr
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setDiscountFraction(1)
.build());
PackagePromotion packagePromotion =
new PackagePromotion.Builder()
BulkPricingPackage bulkPricingPackage =
new BulkPricingPackage.Builder()
.setToken(token)
.setMaxDomains(100)
.setMaxCreates(500)
.setPackagePrice(Money.of(CurrencyUnit.USD, 1000))
.setBulkPrice(Money.of(CurrencyUnit.USD, 1000))
.setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z"))
.setLastNotificationSent(DateTime.parse("2010-11-12T05:00:00Z"))
.build();
tm().transact(() -> tm().put(packagePromotion));
tm().transact(() -> tm().put(bulkPricingPackage));
runCommand("abc123");
}
@Test
void testSuccessMultiplePackages() throws Exception {
void testSuccessMultipleBulkPricingPackages() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
@@ -81,11 +82,11 @@ public class GetPackagePromotionCommandTest extends CommandTestCase<GetPackagePr
tm().transact(
() ->
tm().put(
new PackagePromotion.Builder()
new BulkPricingPackage.Builder()
.setToken(token)
.setMaxDomains(100)
.setMaxCreates(500)
.setPackagePrice(Money.of(CurrencyUnit.USD, 1000))
.setBulkPrice(Money.of(CurrencyUnit.USD, 1000))
.setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z"))
.setLastNotificationSent(DateTime.parse("2010-11-12T05:00:00Z"))
.build()));
@@ -93,7 +94,7 @@ public class GetPackagePromotionCommandTest extends CommandTestCase<GetPackagePr
persistResource(
new AllocationToken.Builder()
.setToken("123abc")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2012-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
@@ -103,11 +104,11 @@ public class GetPackagePromotionCommandTest extends CommandTestCase<GetPackagePr
tm().transact(
() ->
tm().put(
new PackagePromotion.Builder()
new BulkPricingPackage.Builder()
.setToken(token2)
.setMaxDomains(1000)
.setMaxCreates(700)
.setPackagePrice(Money.of(CurrencyUnit.USD, 3000))
.setBulkPrice(Money.of(CurrencyUnit.USD, 3000))
.setNextBillingDate(DateTime.parse("2014-11-12T05:00:00Z"))
.setLastNotificationSent(DateTime.parse("2013-11-12T05:00:00Z"))
.build()));
@@ -116,12 +117,12 @@ public class GetPackagePromotionCommandTest extends CommandTestCase<GetPackagePr
}
@Test
void testFailure_packageDoesNotExist() {
void testFailure_bulkPricingPackageDoesNotExist() {
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> runCommand("fakeToken"));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("PackagePromotion with package token fakeToken does not exist");
.isEqualTo("BulkPricingPackage with token fakeToken does not exist");
}
@Test

View File

@@ -22,7 +22,7 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.CAN
import static google.registry.model.domain.token.AllocationToken.TokenStatus.ENDED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT_STARTED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
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.testing.DatabaseHelper.createTld;
@@ -344,13 +344,13 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
}
@Test
void testUpdateStatusTransitions_endPackageTokenNoDomains() throws Exception {
void testUpdateStatusTransitions_endBulkTokenNoDomains() throws Exception {
DateTime now = fakeClock.nowUtc();
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("token")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setTokenStatusTransitions(
@@ -371,13 +371,13 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
}
@Test
void testUpdateStatusTransitions_endPackageTokenWithActiveDomainsFails() throws Exception {
void testUpdateStatusTransitions_endBulkTokenWithActiveDomainsFails() throws Exception {
DateTime now = fakeClock.nowUtc();
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("token")
.setTokenType(PACKAGE)
.setTokenType(BULK_PRICING)
.setRenewalPriceBehavior(SPECIFIED)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setTokenStatusTransitions(
@@ -390,7 +390,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
persistResource(
persistActiveDomain("example.tld")
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.setCurrentBulkToken(token.createVKey())
.build());
IllegalArgumentException thrown =
assertThrows(
@@ -406,8 +406,8 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Package token token can not end its promotion because it still has 1 domains in the"
+ " package");
"Bulk token token can not end its promotion because it still has 1 domains in the"
+ " promotion");
}
@Test

View File

@@ -23,7 +23,7 @@ import com.google.common.truth.Truth;
import google.registry.model.billing.BillingBase.RenewalPriceBehavior;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.PackagePromotion;
import google.registry.model.domain.token.BulkPricingPackage;
import java.util.Optional;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
@@ -32,9 +32,9 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
/** Unit tests for {@link google.registry.tools.UpdatePackagePromotionCommand}. */
public class UpdatePackagePromotionCommandTest
extends CommandTestCase<UpdatePackagePromotionCommand> {
/** Unit tests for {@link UpdateBulkPricingPackageCommand}. */
public class UpdateBulkPricingPackageCommandTest
extends CommandTestCase<UpdateBulkPricingPackageCommand> {
@BeforeEach
void beforeEach() {
@@ -42,23 +42,23 @@ public class UpdatePackagePromotionCommandTest
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setDiscountFraction(1)
.build());
PackagePromotion packagePromotion =
new PackagePromotion.Builder()
BulkPricingPackage bulkPricingPackage =
new BulkPricingPackage.Builder()
.setToken(token)
.setMaxDomains(100)
.setMaxCreates(500)
.setPackagePrice(Money.of(CurrencyUnit.USD, 1000))
.setBulkPrice(Money.of(CurrencyUnit.USD, 1000))
.setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z"))
.setLastNotificationSent(DateTime.parse("2010-11-12T05:00:00Z"))
.build();
tm().transact(() -> tm().put(packagePromotion));
tm().transact(() -> tm().put(bulkPricingPackage));
}
@Test
@@ -71,25 +71,24 @@ public class UpdatePackagePromotionCommandTest
"--clear_last_notification_sent",
"abc123");
Optional<PackagePromotion> packagePromotionOptional =
tm().transact(() -> PackagePromotion.loadByTokenString("abc123"));
assertThat(packagePromotionOptional).isPresent();
PackagePromotion packagePromotion = packagePromotionOptional.get();
Truth.assertThat(packagePromotion.getMaxDomains()).isEqualTo(200);
Truth.assertThat(packagePromotion.getMaxCreates()).isEqualTo(1000);
Truth.assertThat(packagePromotion.getPackagePrice())
.isEqualTo(Money.of(CurrencyUnit.USD, 2000));
Truth.assertThat(packagePromotion.getNextBillingDate())
Optional<BulkPricingPackage> bulkPricingPackageOptional =
tm().transact(() -> BulkPricingPackage.loadByTokenString("abc123"));
assertThat(bulkPricingPackageOptional).isPresent();
BulkPricingPackage bulkPricingPackage = bulkPricingPackageOptional.get();
Truth.assertThat(bulkPricingPackage.getMaxDomains()).isEqualTo(200);
Truth.assertThat(bulkPricingPackage.getMaxCreates()).isEqualTo(1000);
Truth.assertThat(bulkPricingPackage.getBulkPrice()).isEqualTo(Money.of(CurrencyUnit.USD, 2000));
Truth.assertThat(bulkPricingPackage.getNextBillingDate())
.isEqualTo(DateTime.parse("2013-03-17T00:00:00Z"));
assertThat(packagePromotion.getLastNotificationSent()).isEmpty();
assertThat(bulkPricingPackage.getLastNotificationSent()).isEmpty();
}
@Test
void testFailure_packageDoesNotExist() throws Exception {
void testFailure_bulkPackageDoesNotExist() throws Exception {
persistResource(
new AllocationToken.Builder()
.setToken("nullPackage")
.setTokenType(TokenType.PACKAGE)
.setTokenType(TokenType.BULK_PRICING)
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setAllowedTlds(ImmutableSet.of("foo"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
@@ -107,7 +106,7 @@ public class UpdatePackagePromotionCommandTest
"--next_billing_date=2012-03-17",
"nullPackage"));
Truth.assertThat(thrown.getMessage())
.isEqualTo("PackagePromotion with token nullPackage does not exist");
.isEqualTo("BulkPricingPackage with token nullPackage does not exist");
}
@Test
@@ -119,17 +118,16 @@ public class UpdatePackagePromotionCommandTest
"--clear_last_notification_sent",
"abc123");
Optional<PackagePromotion> packagePromotionOptional =
tm().transact(() -> PackagePromotion.loadByTokenString("abc123"));
assertThat(packagePromotionOptional).isPresent();
PackagePromotion packagePromotion = packagePromotionOptional.get();
Truth.assertThat(packagePromotion.getMaxDomains()).isEqualTo(100);
Truth.assertThat(packagePromotion.getMaxCreates()).isEqualTo(1000);
Truth.assertThat(packagePromotion.getPackagePrice())
.isEqualTo(Money.of(CurrencyUnit.USD, 2000));
Truth.assertThat(packagePromotion.getNextBillingDate())
Optional<BulkPricingPackage> bulkPricingPackageOptional =
tm().transact(() -> BulkPricingPackage.loadByTokenString("abc123"));
assertThat(bulkPricingPackageOptional).isPresent();
BulkPricingPackage bulkPricingPackage = bulkPricingPackageOptional.get();
Truth.assertThat(bulkPricingPackage.getMaxDomains()).isEqualTo(100);
Truth.assertThat(bulkPricingPackage.getMaxCreates()).isEqualTo(1000);
Truth.assertThat(bulkPricingPackage.getBulkPrice()).isEqualTo(Money.of(CurrencyUnit.USD, 2000));
Truth.assertThat(bulkPricingPackage.getNextBillingDate())
.isEqualTo(DateTime.parse("2013-03-17T00:00:00Z"));
assertThat(packagePromotion.getLastNotificationSent()).isEmpty();
assertThat(bulkPricingPackage.getLastNotificationSent()).isEmpty();
}
@Test
@@ -141,17 +139,16 @@ public class UpdatePackagePromotionCommandTest
"--clear_last_notification_sent",
"abc123");
Optional<PackagePromotion> packagePromotionOptional =
tm().transact(() -> PackagePromotion.loadByTokenString("abc123"));
assertThat(packagePromotionOptional).isPresent();
PackagePromotion packagePromotion = packagePromotionOptional.get();
Truth.assertThat(packagePromotion.getMaxDomains()).isEqualTo(200);
Truth.assertThat(packagePromotion.getMaxCreates()).isEqualTo(1000);
Truth.assertThat(packagePromotion.getPackagePrice())
.isEqualTo(Money.of(CurrencyUnit.USD, 2000));
Truth.assertThat(packagePromotion.getNextBillingDate())
Optional<BulkPricingPackage> bulkPricingPackageOptional =
tm().transact(() -> BulkPricingPackage.loadByTokenString("abc123"));
assertThat(bulkPricingPackageOptional).isPresent();
BulkPricingPackage bulkPricingPackage = bulkPricingPackageOptional.get();
Truth.assertThat(bulkPricingPackage.getMaxDomains()).isEqualTo(200);
Truth.assertThat(bulkPricingPackage.getMaxCreates()).isEqualTo(1000);
Truth.assertThat(bulkPricingPackage.getBulkPrice()).isEqualTo(Money.of(CurrencyUnit.USD, 2000));
Truth.assertThat(bulkPricingPackage.getNextBillingDate())
.isEqualTo(DateTime.parse("2012-11-12T05:00:00Z"));
assertThat(packagePromotion.getLastNotificationSent()).isEmpty();
assertThat(bulkPricingPackage.getLastNotificationSent()).isEmpty();
}
@Test
@@ -163,34 +160,32 @@ public class UpdatePackagePromotionCommandTest
"--clear_last_notification_sent",
"abc123");
Optional<PackagePromotion> packagePromotionOptional =
tm().transact(() -> PackagePromotion.loadByTokenString("abc123"));
assertThat(packagePromotionOptional).isPresent();
PackagePromotion packagePromotion = packagePromotionOptional.get();
Truth.assertThat(packagePromotion.getMaxDomains()).isEqualTo(200);
Truth.assertThat(packagePromotion.getMaxCreates()).isEqualTo(1000);
Truth.assertThat(packagePromotion.getPackagePrice())
.isEqualTo(Money.of(CurrencyUnit.USD, 1000));
Truth.assertThat(packagePromotion.getNextBillingDate())
Optional<BulkPricingPackage> bulkPricingPackageOptional =
tm().transact(() -> BulkPricingPackage.loadByTokenString("abc123"));
assertThat(bulkPricingPackageOptional).isPresent();
BulkPricingPackage bulkPricingPackage = bulkPricingPackageOptional.get();
Truth.assertThat(bulkPricingPackage.getMaxDomains()).isEqualTo(200);
Truth.assertThat(bulkPricingPackage.getMaxCreates()).isEqualTo(1000);
Truth.assertThat(bulkPricingPackage.getBulkPrice()).isEqualTo(Money.of(CurrencyUnit.USD, 1000));
Truth.assertThat(bulkPricingPackage.getNextBillingDate())
.isEqualTo(DateTime.parse("2013-03-17T00:00:00Z"));
assertThat(packagePromotion.getLastNotificationSent()).isEmpty();
assertThat(bulkPricingPackage.getLastNotificationSent()).isEmpty();
}
@Test
void testSuccess_dontClearLastNotificationSent() throws Exception {
runCommandForced("--max_domains=200", "--max_creates=1000", "--price=USD 2000.00", "abc123");
Optional<PackagePromotion> packagePromotionOptional =
tm().transact(() -> PackagePromotion.loadByTokenString("abc123"));
assertThat(packagePromotionOptional).isPresent();
PackagePromotion packagePromotion = packagePromotionOptional.get();
Truth.assertThat(packagePromotion.getMaxDomains()).isEqualTo(200);
Truth.assertThat(packagePromotion.getMaxCreates()).isEqualTo(1000);
Truth.assertThat(packagePromotion.getPackagePrice())
.isEqualTo(Money.of(CurrencyUnit.USD, 2000));
Truth.assertThat(packagePromotion.getNextBillingDate())
Optional<BulkPricingPackage> bulkPricingPackageOptional =
tm().transact(() -> BulkPricingPackage.loadByTokenString("abc123"));
assertThat(bulkPricingPackageOptional).isPresent();
BulkPricingPackage bulkPricingPackage = bulkPricingPackageOptional.get();
Truth.assertThat(bulkPricingPackage.getMaxDomains()).isEqualTo(200);
Truth.assertThat(bulkPricingPackage.getMaxCreates()).isEqualTo(1000);
Truth.assertThat(bulkPricingPackage.getBulkPrice()).isEqualTo(Money.of(CurrencyUnit.USD, 2000));
Truth.assertThat(bulkPricingPackage.getNextBillingDate())
.isEqualTo(DateTime.parse("2012-11-12T05:00:00Z"));
Truth.assertThat(packagePromotion.getLastNotificationSent().get())
Truth.assertThat(bulkPricingPackage.getLastNotificationSent().get())
.isEqualTo(DateTime.parse("2010-11-12T05:00:00.000Z"));
}
}

View File

@@ -0,0 +1,174 @@
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server.console.settings;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.api.client.http.HttpStatusCodes;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.request.RequestModule;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.AuthSettings.AuthLevel;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
import google.registry.request.auth.UserAuthInfo;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Tests for {@link WhoisRegistrarFieldsAction}. */
public class WhoisRegistrarFieldsActionTest {
private static final Gson GSON = RequestModule.provideGson();
private final FakeClock clock = new FakeClock(DateTime.parse("2023-08-01T00:00:00.000Z"));
private final FakeResponse fakeResponse = new FakeResponse();
private final HttpServletRequest request = mock(HttpServletRequest.class);
private final AuthenticatedRegistrarAccessor registrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of("TheRegistrar", Role.OWNER, "NewRegistrar", Role.OWNER));
private final HashMap<String, Object> uiRegistrarMap =
Maps.newHashMap(
ImmutableMap.of(
"registrarId",
"TheRegistrar",
"whoisServer",
"whois.nic.google",
"type",
"REAL",
"emailAddress",
"the.registrar@example.com",
"state",
"ACTIVE",
"url",
"\"http://my.fake.url\"",
"localizedAddress",
"{\"street\": [\"123 Example Boulevard\"], \"city\": \"Williamsburg\", \"state\":"
+ " \"NY\", \"zip\": \"11201\", \"countryCode\": \"US\"}"));
@RegisterExtension
final JpaTestExtensions.JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
@Test
void testSuccess_setsAllFields() throws Exception {
Registrar oldRegistrar = Registrar.loadRequiredRegistrarCached("TheRegistrar");
assertThat(oldRegistrar.getWhoisServer()).isEqualTo("whois.nic.fakewhois.example");
assertThat(oldRegistrar.getUrl()).isEqualTo("http://my.fake.url");
ImmutableMap<String, Object> addressMap =
ImmutableMap.of(
"street",
ImmutableList.of("123 Fake St"),
"city",
"Fakeville",
"state",
"NL",
"zip",
"10011",
"countryCode",
"CA");
uiRegistrarMap.putAll(
ImmutableMap.of(
"whoisServer",
"whois.nic.google",
"url",
"\"https://newurl.example\"",
"localizedAddress",
"{\"street\": [\"123 Fake St\"], \"city\": \"Fakeville\", \"state\":"
+ " \"NL\", \"zip\": \"10011\", \"countryCode\": \"CA\"}"));
WhoisRegistrarFieldsAction action = createAction();
action.run();
assertThat(fakeResponse.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_OK);
Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get(); // skip cache
assertThat(newRegistrar.getWhoisServer()).isEqualTo("whois.nic.google");
assertThat(newRegistrar.getUrl()).isEqualTo("https://newurl.example");
assertThat(newRegistrar.getLocalizedAddress().toJsonMap()).isEqualTo(addressMap);
// the non-changed fields should be the same
assertAboutImmutableObjects()
.that(newRegistrar)
.isEqualExceptFields(oldRegistrar, "whoisServer", "url", "localizedAddress");
}
@Test
void testFailure_noAccessToRegistrar() throws Exception {
Registrar newRegistrar = Registrar.loadByRegistrarIdCached("NewRegistrar").get();
AuthResult onlyTheRegistrar =
AuthResult.create(
AuthLevel.USER,
UserAuthInfo.create(
new User.Builder()
.setEmailAddress("email@email.example")
.setUserRoles(
new UserRoles.Builder()
.setRegistrarRoles(
ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT))
.build())
.build()));
uiRegistrarMap.put("registrarId", "NewRegistrar");
WhoisRegistrarFieldsAction action = createAction(onlyTheRegistrar);
action.run();
assertThat(fakeResponse.getStatus()).isEqualTo(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
// should be no change
assertThat(DatabaseHelper.loadByEntity(newRegistrar)).isEqualTo(newRegistrar);
}
private AuthResult defaultUserAuth() {
return AuthResult.create(
AuthLevel.USER,
UserAuthInfo.create(
new User.Builder()
.setEmailAddress("email@email.example")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build()));
}
private WhoisRegistrarFieldsAction createAction() throws IOException {
return createAction(defaultUserAuth());
}
private WhoisRegistrarFieldsAction createAction(AuthResult authResult) throws IOException {
when(request.getReader())
.thenReturn(new BufferedReader(new StringReader(uiRegistrarMap.toString())));
return new WhoisRegistrarFieldsAction(
authResult,
fakeResponse,
GSON,
registrarAccessor,
RegistrarConsoleModule.provideRegistrar(
GSON, RequestModule.provideJsonBody(request, GSON)));
}
}

View File

@@ -33,6 +33,7 @@ import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.groups.GmailClient;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.UserRoles;
import google.registry.model.domain.Domain;
@@ -54,7 +55,6 @@ import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.tools.DomainLockUtils;
import google.registry.util.EmailMessage;
import google.registry.util.SendEmailService;
import google.registry.util.StringGenerator.Alphabets;
import java.util.Map;
import java.util.Optional;
@@ -97,7 +97,7 @@ final class RegistryLockPostActionTest {
private Domain domain;
private RegistryLockPostAction action;
@Mock SendEmailService emailService;
@Mock GmailClient gmailClient;
@Mock HttpServletRequest mockRequest;
@Mock HttpServletResponse mockResponse;
@@ -510,12 +510,12 @@ final class RegistryLockPostActionTest {
private void assertFailureWithMessage(Map<String, ?> response, String message) {
assertThat(response)
.containsExactly("status", "ERROR", "results", ImmutableList.of(), "message", message);
verifyNoMoreInteractions(emailService);
verifyNoMoreInteractions(gmailClient);
}
private void verifyEmail(String recipient) throws Exception {
ArgumentCaptor<EmailMessage> emailCaptor = ArgumentCaptor.forClass(EmailMessage.class);
verify(emailService).sendEmail(emailCaptor.capture());
verify(gmailClient).sendEmail(emailCaptor.capture());
EmailMessage sentMessage = emailCaptor.getValue();
assertThat(sentMessage.subject()).matches("Registry (un)?lock verification");
assertThat(sentMessage.body()).matches(EMAIL_MESSAGE_TEMPLATE);
@@ -545,7 +545,7 @@ final class RegistryLockPostActionTest {
jsonActionRunner,
authResult,
registrarAccessor,
emailService,
gmailClient,
domainLockUtils,
outgoingAddress);
}

View File

@@ -1,14 +1,15 @@
PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY
/_dr/epp EppTlsAction POST n API APP PUBLIC
/console-api/domain ConsoleDomainGetAction GET n API,LEGACY USER PUBLIC
/console-api/registrars RegistrarsAction GET,POST n API,LEGACY USER PUBLIC
/console-api/settings/contacts ContactAction GET,POST n API,LEGACY USER PUBLIC
/console-api/settings/security SecurityAction POST n API,LEGACY USER PUBLIC
/registrar ConsoleUiAction GET n API,LEGACY NONE PUBLIC
/registrar-create ConsoleRegistrarCreatorAction POST,GET n API,LEGACY NONE PUBLIC
/registrar-ote-setup ConsoleOteSetupAction POST,GET n API,LEGACY NONE PUBLIC
/registrar-ote-status OteStatusAction POST n API,LEGACY USER PUBLIC
/registrar-settings RegistrarSettingsAction POST n API,LEGACY USER PUBLIC
/registry-lock-get RegistryLockGetAction GET n API,LEGACY USER PUBLIC
/registry-lock-post RegistryLockPostAction POST n API,LEGACY USER PUBLIC
/registry-lock-verify RegistryLockVerifyAction GET n API,LEGACY NONE PUBLIC
PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY
/_dr/epp EppTlsAction POST n API APP PUBLIC
/console-api/domain ConsoleDomainGetAction GET n API,LEGACY USER PUBLIC
/console-api/registrars RegistrarsAction GET,POST n API,LEGACY USER PUBLIC
/console-api/settings/contacts ContactAction GET,POST n API,LEGACY USER PUBLIC
/console-api/settings/security SecurityAction POST n API,LEGACY USER PUBLIC
/console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n API,LEGACY USER PUBLIC
/registrar ConsoleUiAction GET n API,LEGACY NONE PUBLIC
/registrar-create ConsoleRegistrarCreatorAction POST,GET n API,LEGACY NONE PUBLIC
/registrar-ote-setup ConsoleOteSetupAction POST,GET n API,LEGACY NONE PUBLIC
/registrar-ote-status OteStatusAction POST n API,LEGACY USER PUBLIC
/registrar-settings RegistrarSettingsAction POST n API,LEGACY USER PUBLIC
/registry-lock-get RegistryLockGetAction GET n API,LEGACY USER PUBLIC
/registry-lock-post RegistryLockPostAction POST n API,LEGACY USER PUBLIC
/registry-lock-verify RegistryLockVerifyAction GET n API,LEGACY NONE PUBLIC

View File

@@ -498,12 +498,12 @@
create table "PackagePromotion" (
package_promotion_id bigserial not null,
package_price_amount numeric(19, 2) not null,
package_price_currency text not null,
last_notification_sent timestamptz,
max_creates int4 not null,
max_domains int4 not null,
next_billing_date timestamptz not null,
package_price_amount numeric(19, 2) not null,
package_price_currency text not null,
token text not null,
primary key (package_promotion_id)
);

View File

@@ -311,7 +311,7 @@ An EPP flow that creates a new domain resource.
* Registrar is not logged in.
* The current registry phase allows registrations only with signed marks.
* The current registry phase does not allow for general registrations.
* Package domain registered for too many years.
* Bulk pricing domain registered for too many years.
* Signed marks are only allowed during sunrise.
* An allocation token was provided that is invalid for premium domains.
* 2003
@@ -499,8 +499,8 @@ comes in at the exact millisecond that the domain would have expired.
* Resource status prohibits this operation.
* The allocation token is not currently valid.
* 2305
* The __REMOVEDOMAIN__ token is missing on a package domain command
* The __REMOVEDOMAIN__ token is not allowed on non package domains
* The __REMOVEDOMAIN__ token is missing on a bulk pricing domain command
* The __REMOVEDOMAIN__ token is not allowed on non bulk pricing domains
* The allocation token is not valid for this domain.
* The allocation token is not valid for this registrar.
* The allocation token is not valid for this TLD.