diff --git a/core/src/main/java/google/registry/model/domain/token/PackagePromotion.java b/core/src/main/java/google/registry/model/domain/token/PackagePromotion.java index a71d8e1d7..f0db57fd4 100644 --- a/core/src/main/java/google/registry/model/domain/token/PackagePromotion.java +++ b/core/src/main/java/google/registry/model/domain/token/PackagePromotion.java @@ -15,6 +15,7 @@ package google.registry.model.domain.token; import static com.google.common.base.Preconditions.checkArgument; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; @@ -94,6 +95,21 @@ 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 loadByTokenString(String tokenString) { + jpaTm().assertInTransaction(); + return jpaTm() + .query("FROM PackagePromotion WHERE token = :token", PackagePromotion.class) + .setParameter("token", VKey.createSql(AllocationToken.class, tokenString)) + .getResultStream() + .findFirst(); + } + + @Override + public VKey createVKey() { + return VKey.createSql(PackagePromotion.class, packagePromotionId); + } + @Override public Builder asBuilder() { return new Builder(clone(this)); @@ -143,7 +159,7 @@ public class PackagePromotion extends ImmutableObject implements Buildable { return this; } - public Builder setNextBillingDate(@Nullable DateTime nextBillingDate) { + public Builder setNextBillingDate(DateTime nextBillingDate) { checkArgumentNotNull(nextBillingDate, "Next billing date must not be null"); getInstance().nextBillingDate = nextBillingDate; return this; diff --git a/core/src/main/java/google/registry/tools/CreateOrUpdatePackagePromotionCommand.java b/core/src/main/java/google/registry/tools/CreateOrUpdatePackagePromotionCommand.java new file mode 100644 index 000000000..7cc10f5d8 --- /dev/null +++ b/core/src/main/java/google/registry/tools/CreateOrUpdatePackagePromotionCommand.java @@ -0,0 +1,120 @@ +// 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.tools; + +import static com.google.common.base.Preconditions.checkArgument; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; + +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.persistence.VKey; +import java.util.Date; +import java.util.List; +import java.util.Optional; +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 { + + @Parameter(description = "Allocation token String of the package token", required = true) + List mainParameters; + + @Nullable + @Parameter( + names = {"-d", "--max_domains"}, + description = "Maximum concurrent active domains allowed in the package") + Integer maxDomains; + + @Nullable + @Parameter( + names = {"-c", "--max_creates"}, + description = "Maximum domain creations allowed in the package each year") + Integer maxCreates; + + @Nullable + @Parameter( + names = {"-p", "--price"}, + description = "Annual price of the package") + Money price; + + @Nullable + @Parameter( + names = "--next_billing_date", + description = "The next date that the package should be billed for its annual fee") + Date nextBillingDate; + + /** Returns the existing PackagePromotion or null if it does not exist. */ + @Nullable + abstract PackagePromotion getOldPackagePromotion(String token); + + /** Returns the allocation token object. */ + AllocationToken getAndCheckAllocationToken(String token) { + Optional allocationToken = + tm().transact(() -> tm().loadByKeyIfPresent(VKey.createSql(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", + token); + checkArgument( + allocationToken.get().getTokenType().equals(TokenType.PACKAGE), + "The allocation token must be of the PACKAGE token type"); + return allocationToken.get(); + } + + /** Does not clear the lastNotificationSent field. Subclasses can override this. */ + boolean clearLastNotificationSent() { + return false; + } + + @Override + protected final void init() throws Exception { + for (String token : mainParameters) { + jpaTm() + .transact( + () -> { + PackagePromotion oldPackage = getOldPackagePromotion(token); + checkArgument( + oldPackage != null || price != null, + "PackagePrice is required when creating a new package"); + + AllocationToken allocationToken = getAndCheckAllocationToken(token); + + PackagePromotion.Builder builder = + (oldPackage == null) + ? new PackagePromotion.Builder().setToken(allocationToken) + : oldPackage.asBuilder(); + + Optional.ofNullable(maxDomains).ifPresent(builder::setMaxDomains); + Optional.ofNullable(maxCreates).ifPresent(builder::setMaxCreates); + Optional.ofNullable(price).ifPresent(builder::setPackagePrice); + Optional.ofNullable(nextBillingDate) + .ifPresent( + nextBillingDate -> + builder.setNextBillingDate(new DateTime(nextBillingDate))); + if (clearLastNotificationSent()) { + builder.setLastNotificationSent(null); + } + PackagePromotion newPackage = builder.build(); + stageEntityChange(oldPackage, newPackage); + }); + } + } +} diff --git a/core/src/main/java/google/registry/tools/CreatePackagePromotionCommand.java b/core/src/main/java/google/registry/tools/CreatePackagePromotionCommand.java new file mode 100644 index 000000000..ff68a1f28 --- /dev/null +++ b/core/src/main/java/google/registry/tools/CreatePackagePromotionCommand.java @@ -0,0 +1,39 @@ +// 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.tools; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.beust.jcommander.Parameters; +import google.registry.model.domain.token.PackagePromotion; +import org.jetbrains.annotations.Nullable; + +/** Command to create a PackagePromotion */ +@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 { + + @Nullable + @Override + PackagePromotion getOldPackagePromotion(String tokenString) { + checkArgument( + !PackagePromotion.loadByTokenString(tokenString).isPresent(), + "PackagePromotion with token %s already exists", + tokenString); + return null; + } +} diff --git a/core/src/main/java/google/registry/tools/RegistryTool.java b/core/src/main/java/google/registry/tools/RegistryTool.java index c1475d9b1..25586c24d 100644 --- a/core/src/main/java/google/registry/tools/RegistryTool.java +++ b/core/src/main/java/google/registry/tools/RegistryTool.java @@ -42,6 +42,7 @@ 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) @@ -106,6 +107,7 @@ public final class RegistryTool { .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_registrar", UpdateRegistrarCommand.class) .put("update_reserved_list", UpdateReservedListCommand.class) diff --git a/core/src/main/java/google/registry/tools/UpdatePackagePromotionCommand.java b/core/src/main/java/google/registry/tools/UpdatePackagePromotionCommand.java new file mode 100644 index 000000000..adade8cbe --- /dev/null +++ b/core/src/main/java/google/registry/tools/UpdatePackagePromotionCommand.java @@ -0,0 +1,45 @@ +// 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.tools; + +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 java.util.Optional; + +/** Command to update a PackagePromotion */ +@Parameters(separators = " =", commandDescription = "Update package promotion object(s)") +public final class UpdatePackagePromotionCommand extends CreateOrUpdatePackagePromotionCommand { + + @Parameter( + names = "--clear_last_notification_sent", + description = + "Clear the date last max-domain non-compliance notification was sent? (This should be" + + " cleared whenever a tier is upgraded)") + boolean clearLastNotificationSent; + + @Override + PackagePromotion getOldPackagePromotion(String token) { + Optional oldPackage = PackagePromotion.loadByTokenString(token); + checkArgument(oldPackage.isPresent(), "PackagePromotion with token %s does not exist", token); + return oldPackage.get(); + } + + @Override + boolean clearLastNotificationSent() { + return clearLastNotificationSent; + } +} diff --git a/core/src/test/java/google/registry/tools/CreatePackagePromotionCommandTest.java b/core/src/test/java/google/registry/tools/CreatePackagePromotionCommandTest.java new file mode 100644 index 000000000..db7641cbd --- /dev/null +++ b/core/src/test/java/google/registry/tools/CreatePackagePromotionCommandTest.java @@ -0,0 +1,222 @@ +// 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.tools; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; +import static google.registry.testing.DatabaseHelper.persistResource; +import static google.registry.util.DateTimeUtils.END_OF_TIME; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import google.registry.model.billing.BillingEvent.RenewalPriceBehavior; +import google.registry.model.domain.token.AllocationToken; +import google.registry.model.domain.token.AllocationToken.TokenType; +import google.registry.model.domain.token.PackagePromotion; +import java.util.Optional; +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; +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 { + + @Test + void testSuccess() throws Exception { + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .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()); + runCommandForced( + "--max_domains=100", + "--max_creates=500", + "--price=USD 1000.00", + "--next_billing_date=2012-03-17", + "abc123"); + + Optional packagePromotionOptional = + jpaTm().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(DateTime.parse("2012-03-17T00:00:00Z")); + assertThat(packagePromotion.getLastNotificationSent()).isEmpty(); + } + + @Test + void testFailure_tokenIsNotPackageType() throws Exception { + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .setTokenType(TokenType.SINGLE_USE) + .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) + .setAllowedTlds(ImmutableSet.of("foo")) + .setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar")) + .setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED) + .setDiscountFraction(1) + .build()); + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> + runCommandForced( + "--max_domains=100", + "--max_creates=500", + "--price=USD 1000.00", + "--next_billing_date=2012-03-17T05:00:00Z", + "abc123")); + assertThat(thrown.getMessage()) + .isEqualTo("The allocation token must be of the PACKAGE token type"); + } + + @Test + void testFailure_tokenDoesNotExist() throws Exception { + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> + runCommandForced( + "--max_domains=100", + "--max_creates=500", + "--price=USD 1000.00", + "--next_billing_date=2012-03-17T05:00:00Z", + "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"); + } + + @Test + void testFailure_packagePromotionAlreadyExists() throws Exception { + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .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()); + runCommandForced( + "--max_domains=100", + "--max_creates=500", + "--price=USD 1000.00", + "--next_billing_date=2012-03-17T05:00:00Z", + "abc123"); + Optional packagePromotionOptional = + jpaTm().transact(() -> PackagePromotion.loadByTokenString("abc123")); + assertThat(packagePromotionOptional).isPresent(); + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> + runCommandForced( + "--max_domains=100", + "--max_creates=500", + "--price=USD 1000.00", + "--next_billing_date=2012-03-17T05:00:00Z", + "abc123")); + assertThat(thrown.getMessage()).isEqualTo("PackagePromotion with token abc123 already exists"); + } + + @Test + void testSuccess_missingMaxDomainsAndCreatesInitializesToZero() throws Exception { + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .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()); + runCommandForced("--price=USD 1000.00", "--next_billing_date=2012-03-17", "abc123"); + Optional packagePromotionOptional = + jpaTm().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()) + .isEqualTo(DateTime.parse("2012-03-17T00:00:00Z")); + assertThat(packagePromotion.getLastNotificationSent()).isEmpty(); + } + + @Test + void testSuccess_missingNextBillingDateInitializesToEndOfTime() throws Exception { + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .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()); + runCommandForced("--max_domains=100", "--max_creates=500", "--price=USD 1000.00", "abc123"); + + Optional packagePromotionOptional = + jpaTm().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(); + } + + @Test + void testFailure_missingPrice() throws Exception { + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .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()); + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> + runCommandForced( + "--max_domains=100", + "--max_creates=500", + "--next_billing_date=2012-03-17T05:00:00Z", + "abc123")); + assertThat(thrown.getMessage()) + .isEqualTo("PackagePrice is required when creating a new package"); + } +} diff --git a/core/src/test/java/google/registry/tools/UpdatePackagePromotionCommandTest.java b/core/src/test/java/google/registry/tools/UpdatePackagePromotionCommandTest.java new file mode 100644 index 000000000..db0506955 --- /dev/null +++ b/core/src/test/java/google/registry/tools/UpdatePackagePromotionCommandTest.java @@ -0,0 +1,196 @@ +// 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.tools; + +import static com.google.common.truth.Truth8.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; +import static google.registry.testing.DatabaseHelper.persistResource; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.common.truth.Truth; +import google.registry.model.billing.BillingEvent.RenewalPriceBehavior; +import google.registry.model.domain.token.AllocationToken; +import google.registry.model.domain.token.AllocationToken.TokenType; +import google.registry.model.domain.token.PackagePromotion; +import java.util.Optional; +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; +import org.testcontainers.shaded.com.google.common.collect.ImmutableSet; + +/** Unit tests for {@link google.registry.tools.UpdatePackagePromotionCommand}. */ +public class UpdatePackagePromotionCommandTest + extends CommandTestCase { + + @BeforeEach + void beforeEach() { + AllocationToken token = + persistResource( + new AllocationToken.Builder() + .setToken("abc123") + .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 packagePromotion = + new PackagePromotion.Builder() + .setToken(token) + .setMaxDomains(100) + .setMaxCreates(500) + .setPackagePrice(Money.of(CurrencyUnit.USD, 1000)) + .setNextBillingDate(DateTime.parse("2012-11-12T05:00:00Z")) + .setLastNotificationSent(DateTime.parse("2010-11-12T05:00:00Z")) + .build(); + persistResource(packagePromotion); + } + + @Test + void testSuccess() throws Exception { + runCommandForced( + "--max_domains=200", + "--max_creates=1000", + "--price=USD 2000.00", + "--next_billing_date=2013-03-17", + "--clear_last_notification_sent", + "abc123"); + + Optional packagePromotionOptional = + jpaTm().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()) + .isEqualTo(DateTime.parse("2013-03-17T00:00:00Z")); + assertThat(packagePromotion.getLastNotificationSent()).isEmpty(); + } + + @Test + void testFailure_packageDoesNotExist() throws Exception { + persistResource( + new AllocationToken.Builder() + .setToken("nullPackage") + .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()); + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> + runCommandForced( + "--max_domains=100", + "--max_creates=500", + "--price=USD 1000.00", + "--next_billing_date=2012-03-17", + "nullPackage")); + Truth.assertThat(thrown.getMessage()) + .isEqualTo("PackagePromotion with token nullPackage does not exist"); + } + + @Test + void testSuccess_missingMaxDomains() throws Exception { + runCommandForced( + "--max_creates=1000", + "--price=USD 2000.00", + "--next_billing_date=2013-03-17", + "--clear_last_notification_sent", + "abc123"); + + Optional packagePromotionOptional = + jpaTm().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()) + .isEqualTo(DateTime.parse("2013-03-17T00:00:00Z")); + assertThat(packagePromotion.getLastNotificationSent()).isEmpty(); + } + + @Test + void testSuccess_missingNextBillingDate() throws Exception { + runCommandForced( + "--max_domains=200", + "--max_creates=1000", + "--price=USD 2000.00", + "--clear_last_notification_sent", + "abc123"); + + Optional packagePromotionOptional = + jpaTm().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()) + .isEqualTo(DateTime.parse("2012-11-12T05:00:00Z")); + assertThat(packagePromotion.getLastNotificationSent()).isEmpty(); + } + + @Test + void testSuccess_missingPrice() throws Exception { + runCommandForced( + "--max_domains=200", + "--max_creates=1000", + "--next_billing_date=2013-03-17", + "--clear_last_notification_sent", + "abc123"); + + Optional packagePromotionOptional = + jpaTm().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()) + .isEqualTo(DateTime.parse("2013-03-17T00:00:00Z")); + assertThat(packagePromotion.getLastNotificationSent()).isEmpty(); + } + + @Test + void testSuccess_dontClearLastNotificationSent() throws Exception { + runCommandForced("--max_domains=200", "--max_creates=1000", "--price=USD 2000.00", "abc123"); + + Optional packagePromotionOptional = + jpaTm().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()) + .isEqualTo(DateTime.parse("2012-11-12T05:00:00Z")); + Truth.assertThat(packagePromotion.getLastNotificationSent().get()) + .isEqualTo(DateTime.parse("2010-11-12T05:00:00.000Z")); + } +}