diff --git a/core/src/main/java/google/registry/batch/CheckPackagesComplianceAction.java b/core/src/main/java/google/registry/batch/CheckPackagesComplianceAction.java index 53a0ecb29..1da52311d 100644 --- a/core/src/main/java/google/registry/batch/CheckPackagesComplianceAction.java +++ b/core/src/main/java/google/registry/batch/CheckPackagesComplianceAction.java @@ -15,8 +15,10 @@ package google.registry.batch; import static com.google.common.collect.ImmutableList.toImmutableList; 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.Iterables; import com.google.common.flogger.FluentLogger; import com.google.common.primitives.Ints; import google.registry.config.RegistryConfig.Config; @@ -28,8 +30,10 @@ 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 @@ -44,77 +48,164 @@ 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 String packageCreateLimitEmailSubjectText; - private final String packageCreateLimitEmailBodyText; + 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, - @Config("packageCreateLimitEmailSubjectText") String packageCreateLimitEmailSubjectText, - @Config("packageCreateLimitEmailBodyText") String packageCreateLimitEmailBodyText, + 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.packageCreateLimitEmailSubjectText = packageCreateLimitEmailSubjectText; - this.packageCreateLimitEmailBodyText = packageCreateLimitEmailBodyText; + 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( - () -> { - ImmutableList packages = tm().loadAllOf(PackagePromotion.class); - ImmutableList.Builder packagesOverCreateLimit = - new ImmutableList.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()) { - int overage = Ints.saturatedCast(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); - packagesOverCreateLimit.add(packagePromo); - } - } - if (packagesOverCreateLimit.build().isEmpty()) { - logger.atInfo().log("Found no packages over their create limit."); - } else { - logger.atInfo().log( - "Found %d packages over their create limit.", - packagesOverCreateLimit.build().size()); - for (PackagePromotion packagePromotion : packagesOverCreateLimit.build()) { - AllocationToken packageToken = tm().loadByKey(packagePromotion.getToken()); - Optional registrar = - Registrar.loadByRegistrarIdCached( - packageToken.getAllowedRegistrarIds().iterator().next()); - if (registrar.isPresent()) { - String body = - String.format( - packageCreateLimitEmailBodyText, - registrar.get().getRegistrarName(), - packageToken.getToken(), - registrySupportEmail); - sendNotification( - packageToken, packageCreateLimitEmailSubjectText, body, registrar.get()); - } else { - logger.atSevere().log( - String.format( - "Could not find registrar for package token %s", packageToken)); - } - } - } - }); + tm().transact(this::checkPackages); + } + + private void checkPackages() { + ImmutableList packages = tm().loadAllOf(PackagePromotion.class); + ImmutableList.Builder packagesOverCreateLimitBuilder = + new ImmutableList.Builder<>(); + ImmutableList.Builder packagesOverActiveDomainsLimitBuilder = + new ImmutableList.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()) { + int overage = Ints.saturatedCast(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.add(packagePromo); + } + + 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.add(packagePromo); + } + } + handlePackageCreationOverage(packagesOverCreateLimitBuilder.build()); + handleActiveDomainOverage(packagesOverActiveDomainsLimitBuilder.build()); + } + + private void handlePackageCreationOverage(ImmutableList 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) { + AllocationToken packageToken = tm().loadByKey(packagePromotion.getToken()); + Optional registrar = + Registrar.loadByRegistrarIdCached( + Iterables.getOnlyElement(packageToken.getAllowedRegistrarIds())); + if (registrar.isPresent()) { + String body = + String.format( + packageCreateLimitEmailBody, + registrar.get().getRegistrarName(), + packageToken.getToken(), + registrySupportEmail); + sendNotification(packageToken, packageCreateLimitEmailSubject, body, registrar.get()); + } else { + throw new IllegalStateException( + String.format("Could not find registrar for package token %s", packageToken)); + } + } + } + + private void handleActiveDomainOverage(ImmutableList 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) { + 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); + } else { + // Send a warning email + sendActiveDomainOverageEmail(/* warning= */ true, packagePromotion); + } + } + } + + private void sendActiveDomainOverageEmail(boolean warning, PackagePromotion packagePromotion) { + String emailSubject = + warning ? packageDomainLimitWarningEmailSubject : packageDomainLimitUpgradeEmailSubject; + String emailTemplate = + warning ? packageDomainLimitWarningEmailBody : packageDomainLimitUpgradeEmailBody; + AllocationToken packageToken = tm().loadByKey(packagePromotion.getToken()); + Optional registrar = + Registrar.loadByRegistrarIdCached( + Iterables.getOnlyElement(packageToken.getAllowedRegistrarIds())); + if (registrar.isPresent()) { + String body = + String.format( + emailTemplate, + registrar.get().getRegistrarName(), + packageToken.getToken(), + registrySupportEmail); + 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( diff --git a/core/src/main/java/google/registry/config/RegistryConfig.java b/core/src/main/java/google/registry/config/RegistryConfig.java index 6d7f288ab..87530ba37 100644 --- a/core/src/main/java/google/registry/config/RegistryConfig.java +++ b/core/src/main/java/google/registry/config/RegistryConfig.java @@ -1317,15 +1317,41 @@ public final class RegistryConfig { } @Provides - @Config("packageCreateLimitEmailSubjectText") - public static String providePackageCreateLimitEmailSubjectText(RegistryConfigSettings config) { - return config.packageMonitoring.packageCreateLimitEmailSubjectText; + @Config("packageCreateLimitEmailSubject") + public static String providePackageCreateLimitEmailSubject(RegistryConfigSettings config) { + return config.packageMonitoring.packageCreateLimitEmailSubject; } @Provides - @Config("packageCreateLimitEmailBodyText") - public static String providePackageCreateLimitEmailBodyText(RegistryConfigSettings config) { - return config.packageMonitoring.packageCreateLimitEmailBodyText; + @Config("packageCreateLimitEmailBody") + public static String providePackageCreateLimitEmailBody(RegistryConfigSettings config) { + return config.packageMonitoring.packageCreateLimitEmailBody; + } + + @Provides + @Config("packageDomainLimitWarningEmailSubject") + public static String providePackageDomainLimitWarningEmailSubject( + RegistryConfigSettings config) { + return config.packageMonitoring.packageDomainLimitWarningEmailSubject; + } + + @Provides + @Config("packageDomainLimitWarningEmailBody") + public static String providePackageDomainLimitWarningEmailBody(RegistryConfigSettings config) { + return config.packageMonitoring.packageDomainLimitWarningEmailBody; + } + + @Provides + @Config("packageDomainLimitUpgradeEmailSubject") + public static String providePackageDomainLimitUpgradeEmailSubject( + RegistryConfigSettings config) { + return config.packageMonitoring.packageDomainLimitUpgradeEmailSubject; + } + + @Provides + @Config("packageDomainLimitUpgradeEmailBody") + public static String providePackageDomainLimitUpgradeEmailBody(RegistryConfigSettings config) { + return config.packageMonitoring.packageDomainLimitUpgradeEmailBody; } } diff --git a/core/src/main/java/google/registry/config/RegistryConfigSettings.java b/core/src/main/java/google/registry/config/RegistryConfigSettings.java index 638443585..a32c41f3f 100644 --- a/core/src/main/java/google/registry/config/RegistryConfigSettings.java +++ b/core/src/main/java/google/registry/config/RegistryConfigSettings.java @@ -255,7 +255,11 @@ public class RegistryConfigSettings { /** Configuration for package compliance monitoring. */ public static class PackageMonitoring { - public String packageCreateLimitEmailSubjectText; - public String packageCreateLimitEmailBodyText; + public String packageCreateLimitEmailSubject; + public String packageCreateLimitEmailBody; + public String packageDomainLimitWarningEmailSubject; + public String packageDomainLimitWarningEmailBody; + public String packageDomainLimitUpgradeEmailSubject; + public String packageDomainLimitUpgradeEmailBody; } } diff --git a/core/src/main/java/google/registry/config/files/default-config.yaml b/core/src/main/java/google/registry/config/files/default-config.yaml index 57cb7b6c3..cd8468bf7 100644 --- a/core/src/main/java/google/registry/config/files/default-config.yaml +++ b/core/src/main/java/google/registry/config/files/default-config.yaml @@ -536,9 +536,9 @@ sslCertificateValidation: # Configuration options for the package compliance monitoring packageMonitoring: # Email subject text to notify partners their package has exceeded the limit for domain creates - packageCreateLimitEmailSubjectText: "NOTICE: Your Package Is Being Upgraded" + packageCreateLimitEmailSubject: "NOTICE: Your package is being upgraded" # Email body text template notify partners their package has exceeded the limit for domain creates - packageCreateLimitEmailBodyText: > + packageCreateLimitEmailBody: > Dear %1$s, We are contacting you to inform you that your package with the package token @@ -550,3 +550,37 @@ packageMonitoring: Regards, Example Registry + + # Email subject text to notify partners their package has exceeded the limit for current active domains and warn them their package will be upgraded in 30 days + packageDomainLimitWarningEmailSubject: "WARNING: Your package has exceeded the domain limit" + # Email body text template to warn partners their package has exceeded the limit for active domains and will be upgraded in 30 days + packageDomainLimitWarningEmailBody: > + Dear %1$s, + + We are contacting you to inform you that your package with the package token + %2$s has exceeded its limit for active domains. + Your package will be upgraded to the next tier in 30 days if the number of active domains does not return below the limit. + + If you have any questions or require additional support, please contact us + at %3$s. + + Regards, + Example Registry + + # Email subject text to notify partners their package has exceeded the limit + # for current active domains for more than 30 days and will be upgraded + packageDomainLimitUpgradeEmailSubject: "NOTICE: Your package is being upgraded" + # Email body text template to warn partners their package has exceeded the + # limit for active domains for more than 30 days and will be upgraded + packageDomainLimitUpgradeEmailBody: > + Dear %1$s, + + We are contacting you to inform you that your package with the package token + %2$s has exceeded its limit for active domains. + Your package will now be upgraded to the next tier. + + If you have any questions or require additional support, please contact us + at %3$s. + + Regards, + Example Registry diff --git a/core/src/test/java/google/registry/batch/CheckPackagesComplianceActionTest.java b/core/src/test/java/google/registry/batch/CheckPackagesComplianceActionTest.java index 7dd94e12e..0d95d686d 100644 --- a/core/src/test/java/google/registry/batch/CheckPackagesComplianceActionTest.java +++ b/core/src/test/java/google/registry/batch/CheckPackagesComplianceActionTest.java @@ -57,7 +57,13 @@ public class CheckPackagesComplianceActionTest { // 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"; + private static final String DOMAIN_LIMIT_WARNING_EMAIL_SUBJECT = "domain limit warning subject"; + private static final String DOMAIN_LIMIT_UPGRADE_EMAIL_SUBJECT = "domain limit upgrade subject"; private static final String CREATE_LIMIT_EMAIL_BODY = "create limit body %1$s %2$s %3$s"; + private static final String DOMAIN_LIMIT_WARNING_EMAIL_BODY = + "domain limit warning body %1$s %2$s %3$s"; + private static final String DOMAIN_LIMIT_UPGRADE_EMAIL_BODY = + "domain limit upgrade body %1$s %2$s %3$s"; private static final String SUPPORT_EMAIL = "registry@test.com"; @RegisterExtension @@ -70,7 +76,6 @@ public class CheckPackagesComplianceActionTest { private final Logger loggerToIntercept = Logger.getLogger(CheckPackagesComplianceAction.class.getCanonicalName()); private final SendEmailService emailService = mock(SendEmailService.class); - private Contact contact; private PackagePromotion packagePromotion; private SendEmailUtils sendEmailUtils; @@ -88,7 +93,15 @@ public class CheckPackagesComplianceActionTest { createTld("tld"); action = new CheckPackagesComplianceAction( - sendEmailUtils, CREATE_LIMIT_EMAIL_SUBJECT, CREATE_LIMIT_EMAIL_BODY, SUPPORT_EMAIL); + sendEmailUtils, + clock, + CREATE_LIMIT_EMAIL_SUBJECT, + DOMAIN_LIMIT_WARNING_EMAIL_SUBJECT, + DOMAIN_LIMIT_UPGRADE_EMAIL_SUBJECT, + CREATE_LIMIT_EMAIL_BODY, + DOMAIN_LIMIT_WARNING_EMAIL_BODY, + DOMAIN_LIMIT_UPGRADE_EMAIL_BODY, + SUPPORT_EMAIL); token = persistResource( new AllocationToken.Builder() @@ -110,7 +123,6 @@ public class CheckPackagesComplianceActionTest { .setLastNotificationSent(DateTime.parse("2010-11-12T05:00:00Z")) .build(); - tm().transact(() -> tm().put(packagePromotion)); contact = persistActiveContact("contact1234"); } @@ -121,6 +133,7 @@ public class CheckPackagesComplianceActionTest { @Test void testSuccess_noPackageOverCreateLimit() { + tm().transact(() -> tm().put(packagePromotion)); persistEppResource( DatabaseHelper.newDomain("foo.tld", contact) .asBuilder() @@ -136,6 +149,7 @@ public class CheckPackagesComplianceActionTest { @Test void testSuccess_onePackageOverCreateLimit() throws Exception { + tm().transact(() -> tm().put(packagePromotion)); // Create limit is 1, creating 2 domains to go over the limit persistEppResource( DatabaseHelper.newDomain("foo.tld", contact) @@ -168,6 +182,7 @@ public class CheckPackagesComplianceActionTest { @Test void testSuccess_multiplePackagesOverCreateLimit() { + tm().transact(() -> tm().put(packagePromotion)); // Create limit is 1, creating 2 domains to go over the limit persistEppResource( DatabaseHelper.newDomain("foo.tld", contact) @@ -234,6 +249,7 @@ public class CheckPackagesComplianceActionTest { @Test void testSuccess_onlyChecksCurrentBillingYear() { + tm().transact(() -> tm().put(packagePromotion)); AllocationToken token2 = persistResource( new AllocationToken.Builder() @@ -273,4 +289,278 @@ public class CheckPackagesComplianceActionTest { .hasLogAtLevelWithMessage(Level.INFO, "Found no packages over their create limit."); verifyNoInteractions(emailService); } + + @Test + void testSuccess_noPackageOverActiveDomainsLimit() { + tm().transact(() -> tm().put(packagePromotion)); + persistEppResource( + DatabaseHelper.newDomain("foo.tld", contact) + .asBuilder() + .setCurrentPackageToken(token.createVKey()) + .build()); + + action.run(); + verifyNoInteractions(emailService); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage(Level.INFO, "Found no packages over their active domains limit."); + } + + @Test + void testSuccess_onePackageOverActiveDomainsLimit() { + packagePromotion = packagePromotion.asBuilder().setMaxCreates(4).setMaxDomains(1).build(); + tm().transact(() -> tm().put(packagePromotion)); + // Domains limit is 1, creating 2 domains to go over the limit + persistEppResource( + DatabaseHelper.newDomain("foo.tld", contact) + .asBuilder() + .setCurrentPackageToken(token.createVKey()) + .build()); + persistEppResource( + DatabaseHelper.newDomain("buzz.tld", contact) + .asBuilder() + .setCurrentPackageToken(token.createVKey()) + .build()); + + AllocationToken token2 = + persistResource( + new AllocationToken.Builder() + .setToken("token") + .setTokenType(TokenType.PACKAGE) + .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() + .setToken(token2) + .setMaxDomains(8) + .setMaxCreates(4) + .setPackagePrice(Money.of(CurrencyUnit.USD, 1000)) + .setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z")) + .build(); + tm().transact(() -> tm().put(packagePromotion2)); + persistEppResource( + DatabaseHelper.newDomain("foo2.tld", contact) + .asBuilder() + .setCurrentPackageToken(token2.createVKey()) + .build()); + + action.run(); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage(Level.INFO, "Found 1 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)."); + 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, "The Registrar", "abc123", SUPPORT_EMAIL)); + PackagePromotion packageAfterCheck = + tm().transact(() -> PackagePromotion.loadByTokenString(token.getToken()).get()); + assertThat(packageAfterCheck.getLastNotificationSent().get()).isEqualTo(clock.nowUtc()); + } + + @Test + void testSuccess_multiplePackagesOverActiveDomainsLimit() { + tm().transact( + () -> tm().put(packagePromotion.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()) + .build()); + persistEppResource( + DatabaseHelper.newDomain("buzz.tld", contact) + .asBuilder() + .setCurrentPackageToken(token.createVKey()) + .build()); + + AllocationToken token2 = + persistResource( + new AllocationToken.Builder() + .setToken("token") + .setTokenType(TokenType.PACKAGE) + .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() + .setToken(token2) + .setMaxDomains(1) + .setMaxCreates(5) + .setPackagePrice(Money.of(CurrencyUnit.USD, 1000)) + .setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z")) + .build(); + tm().transact(() -> tm().put(packagePromotion2)); + + persistEppResource( + DatabaseHelper.newDomain("foo2.tld", contact) + .asBuilder() + .setCurrentPackageToken(token2.createVKey()) + .build()); + persistEppResource( + DatabaseHelper.newDomain("buzz2.tld", contact) + .asBuilder() + .setCurrentPackageToken(token2.createVKey()) + .build()); + + action.run(); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage(Level.INFO, "Found 2 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)."); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage( + Level.INFO, + "Package with package 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 + .asBuilder() + .setMaxCreates(4) + .setMaxDomains(1) + .setLastNotificationSent(clock.nowUtc().minusDays(5)) + .build(); + tm().transact(() -> tm().put(packagePromotion)); + // Domains limit is 1, creating 2 domains to go over the limit + persistEppResource( + DatabaseHelper.newDomain("foo.tld", contact) + .asBuilder() + .setCurrentPackageToken(token.createVKey()) + .build()); + persistEppResource( + DatabaseHelper.newDomain("buzz.tld", contact) + .asBuilder() + .setCurrentPackageToken(token.createVKey()) + .build()); + + action.run(); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage(Level.INFO, "Found 1 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)."); + verifyNoInteractions(emailService); + PackagePromotion packageAfterCheck = + tm().transact(() -> PackagePromotion.loadByTokenString(token.getToken()).get()); + assertThat(packageAfterCheck.getLastNotificationSent().get()) + .isEqualTo(clock.nowUtc().minusDays(5)); + } + + @Test + void testSuccess_packageOverActiveDomainsLimitAlreadySentWarningEmailOver40DaysAgo_SendsAgain() { + packagePromotion = + packagePromotion + .asBuilder() + .setMaxCreates(4) + .setMaxDomains(1) + .setLastNotificationSent(clock.nowUtc().minusDays(45)) + .build(); + tm().transact(() -> tm().put(packagePromotion)); + // Domains limit is 1, creating 2 domains to go over the limit + persistEppResource( + DatabaseHelper.newDomain("foo.tld", contact) + .asBuilder() + .setCurrentPackageToken(token.createVKey()) + .build()); + persistEppResource( + DatabaseHelper.newDomain("buzz.tld", contact) + .asBuilder() + .setCurrentPackageToken(token.createVKey()) + .build()); + + action.run(); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage(Level.INFO, "Found 1 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)."); + 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, "The Registrar", "abc123", SUPPORT_EMAIL)); + PackagePromotion packageAfterCheck = + tm().transact(() -> PackagePromotion.loadByTokenString(token.getToken()).get()); + assertThat(packageAfterCheck.getLastNotificationSent().get()).isEqualTo(clock.nowUtc()); + } + + @Test + void testSuccess_packageOverActiveDomainsLimitAlreadySentWarning30DaysAgo_SendsUpgradeEmail() { + packagePromotion = + packagePromotion + .asBuilder() + .setMaxCreates(4) + .setMaxDomains(1) + .setLastNotificationSent(clock.nowUtc().minusDays(31)) + .build(); + tm().transact(() -> tm().put(packagePromotion)); + // Domains limit is 1, creating 2 domains to go over the limit + persistEppResource( + DatabaseHelper.newDomain("foo.tld", contact) + .asBuilder() + .setCurrentPackageToken(token.createVKey()) + .build()); + persistEppResource( + DatabaseHelper.newDomain("buzz.tld", contact) + .asBuilder() + .setCurrentPackageToken(token.createVKey()) + .build()); + + action.run(); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage(Level.INFO, "Found 1 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)."); + 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, "The Registrar", "abc123", SUPPORT_EMAIL)); + PackagePromotion packageAfterCheck = + tm().transact(() -> PackagePromotion.loadByTokenString(token.getToken()).get()); + assertThat(packageAfterCheck.getLastNotificationSent().get()).isEqualTo(clock.nowUtc()); + } }