From 9665ddbe707dfddeeba8bee7417b53f6ab8c12cb Mon Sep 17 00:00:00 2001 From: Ben McIlwain Date: Wed, 29 Jun 2022 17:39:15 -0400 Subject: [PATCH] Use DomainPricingLogic in ExpandRecurringBillingEventsAction (#1687) * Inject DomainPricingLogic to action file * Remove attempt to inject * Merge conflict * Fix non static to static issue * Merge branch 'master' into at_internal/Expand --- .../ExpandRecurringBillingEventsAction.java | 22 +- .../custom/DomainPricingCustomLogic.java | 3 +- .../flows/domain/DomainPricingLogic.java | 11 +- ...xpandRecurringBillingEventsActionTest.java | 289 ++++++++++++++++++ .../flows/domain/DomainPricingLogicTest.java | 7 +- 5 files changed, 315 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java index 4f2bea2da..dc92aa2f5 100644 --- a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java +++ b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java @@ -26,7 +26,6 @@ import static google.registry.persistence.transaction.QueryComposer.Comparator.E import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm; -import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost; import static google.registry.util.CollectionUtils.union; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.earliestOf; @@ -38,6 +37,7 @@ import com.google.common.collect.Range; import com.google.common.collect.Streams; import com.google.common.flogger.FluentLogger; import google.registry.config.RegistryConfig.Config; +import google.registry.flows.domain.DomainPricingLogic; import google.registry.model.ImmutableObject; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; @@ -61,7 +61,6 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; import javax.inject.Inject; -import org.joda.money.Money; import org.joda.time.DateTime; /** @@ -89,6 +88,7 @@ public class ExpandRecurringBillingEventsAction implements Runnable { @Inject @Parameter(PARAM_DRY_RUN) boolean isDryRun; @Inject @Parameter(PARAM_CURSOR_TIME) Optional cursorTimeParam; + @Inject DomainPricingLogic domainPricingLogic; @Inject Response response; @Inject ExpandRecurringBillingEventsAction() {} @@ -156,7 +156,8 @@ public class ExpandRecurringBillingEventsAction implements Runnable { continue; } int billingEventsSaved = - expandBillingEvent(recurring, executeTime, cursorTime, isDryRun); + expandBillingEvent( + recurring, executeTime, cursorTime, isDryRun, domainPricingLogic); batchBillingEventsSaved += billingEventsSaved; if (billingEventsSaved > 0) { expandedDomains.add(recurring.getTargetId()); @@ -231,7 +232,11 @@ public class ExpandRecurringBillingEventsAction implements Runnable { } private static int expandBillingEvent( - Recurring recurring, DateTime executeTime, DateTime cursorTime, boolean isDryRun) { + Recurring recurring, + DateTime executeTime, + DateTime cursorTime, + boolean isDryRun, + DomainPricingLogic domainPricingLogic) { ImmutableSet.Builder syntheticOneTimesBuilder = new ImmutableSet.Builder<>(); final Registry tld = Registry.get(getTldFromDomainName(recurring.getTargetId())); @@ -295,13 +300,16 @@ public class ExpandRecurringBillingEventsAction implements Runnable { historyEntriesBuilder.add(historyEntry); DateTime eventTime = billingTime.minus(tld.getAutoRenewGracePeriodLength()); - // Determine the cost for a one-year renewal. - Money renewCost = getDomainRenewCost(recurring.getTargetId(), eventTime, 1); + syntheticOneTimesBuilder.add( new OneTime.Builder() .setBillingTime(billingTime) .setRegistrarId(recurring.getRegistrarId()) - .setCost(renewCost) + // Determine the cost for a one-year renewal. + .setCost( + domainPricingLogic + .getRenewPrice(tld, recurring.getTargetId(), eventTime, 1, recurring) + .getRenewCost()) .setEventTime(eventTime) .setFlags(union(recurring.getFlags(), Flag.SYNTHETIC)) .setParent(historyEntry) diff --git a/core/src/main/java/google/registry/flows/custom/DomainPricingCustomLogic.java b/core/src/main/java/google/registry/flows/custom/DomainPricingCustomLogic.java index c80bf1b1f..e701edb63 100644 --- a/core/src/main/java/google/registry/flows/custom/DomainPricingCustomLogic.java +++ b/core/src/main/java/google/registry/flows/custom/DomainPricingCustomLogic.java @@ -50,8 +50,7 @@ public class DomainPricingCustomLogic extends BaseFlowCustomLogic { /** A hook that customizes the renew price. */ @SuppressWarnings("unused") - public FeesAndCredits customizeRenewPrice(RenewPriceParameters priceParameters) - throws EppException { + public FeesAndCredits customizeRenewPrice(RenewPriceParameters priceParameters) { return priceParameters.feesAndCredits(); } diff --git a/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java b/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java index a953aacce..961717667 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java +++ b/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java @@ -51,10 +51,12 @@ import org.joda.time.DateTime; */ public final class DomainPricingLogic { - @Inject DomainPricingCustomLogic customLogic; + private final DomainPricingCustomLogic customLogic; @Inject - DomainPricingLogic() {} + public DomainPricingLogic(DomainPricingCustomLogic customLogic) { + this.customLogic = customLogic; + } /** * Returns a new create price for the pricer. @@ -105,13 +107,12 @@ public final class DomainPricingLogic { } /** Returns a new renewal cost for the pricer. */ - FeesAndCredits getRenewPrice( + public FeesAndCredits getRenewPrice( Registry registry, String domainName, DateTime dateTime, int years, - @Nullable Recurring recurringBillingEvent) - throws EppException { + @Nullable Recurring recurringBillingEvent) { checkArgument(years > 0, "Number of years must be positive"); Money renewCost; boolean isRenewCostPremiumPrice; diff --git a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java index 60904865d..d44ce097c 100644 --- a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java +++ b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java @@ -15,6 +15,8 @@ package google.registry.batch; import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.NONPREMIUM; +import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED; import static google.registry.model.common.Cursor.CursorType.RECURRING_BILLING; import static google.registry.model.domain.Period.Unit.YEARS; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_AUTORENEW; @@ -38,6 +40,8 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Iterables; import com.googlecode.objectify.Key; +import google.registry.flows.custom.DomainPricingCustomLogic; +import google.registry.flows.domain.DomainPricingLogic; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.OneTime; @@ -89,6 +93,8 @@ public class ExpandRecurringBillingEventsActionTest { action.clock = clock; action.cursorTimeParam = Optional.empty(); action.batchSize = 2; + action.domainPricingLogic = + new DomainPricingLogic(new DomainPricingCustomLogic(null, null, null)); createTld("tld"); domain = persistResource( @@ -729,6 +735,112 @@ public class ExpandRecurringBillingEventsActionTest { assertCursorAt(currentTestTime); } + @TestOfyAndSql + void testSuccess_expandMultipleEvents_anchorTenant() throws Exception { + persistResource( + Registry.get("tld") + .asBuilder() + .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100")) + .build()); + recurring = persistResource(recurring.asBuilder().setRenewalPriceBehavior(NONPREMIUM).build()); + BillingEvent.Recurring recurring2 = + persistResource( + recurring + .asBuilder() + .setEventTime(recurring.getEventTime().plusMonths(3)) + .setId(3L) + .build()); + action.cursorTimeParam = Optional.of(START_OF_TIME); + runAction(); + List persistedEntries = + getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); + assertThat(persistedEntries).hasSize(2); + assertHistoryEntryMatches( + domain, + persistedEntries.get(0), + "TheRegistrar", + DateTime.parse("2000-02-19T00:00:00Z"), + true); + BillingEvent.OneTime expected = + defaultOneTimeBuilder() + .setParent(persistedEntries.get(0)) + .setCancellationMatchingBillingEvent(recurring.createVKey()) + .build(); + assertHistoryEntryMatches( + domain, + persistedEntries.get(1), + "TheRegistrar", + DateTime.parse("2000-05-20T00:00:00Z"), + true); + BillingEvent.OneTime expected2 = + defaultOneTimeBuilder() + .setBillingTime(DateTime.parse("2000-05-20T00:00:00Z")) + .setEventTime(DateTime.parse("2000-04-05T00:00:00Z")) + .setParent(persistedEntries.get(1)) + .setCancellationMatchingBillingEvent(recurring2.createVKey()) + .build(); + assertBillingEventsForResource(domain, expected, expected2, recurring, recurring2); + assertCursorAt(currentTestTime); + } + + @TestOfyAndSql + void testSuccess_expandMultipleEvents_premiumDomain_internalRegistration() throws Exception { + persistResource( + Registry.get("tld") + .asBuilder() + .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100")) + .build()); + recurring = + persistResource( + recurring + .asBuilder() + .setRenewalPriceBehavior(SPECIFIED) + .setRenewalPrice(Money.of(USD, 4)) + .build()); + BillingEvent.Recurring recurring2 = + persistResource( + recurring + .asBuilder() + .setEventTime(recurring.getEventTime().plusMonths(3)) + .setId(3L) + .setRenewalPriceBehavior(SPECIFIED) + .setRenewalPrice(Money.of(USD, 4)) + .build()); + action.cursorTimeParam = Optional.of(START_OF_TIME); + runAction(); + List persistedEntries = + getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); + assertThat(persistedEntries).hasSize(2); + assertHistoryEntryMatches( + domain, + persistedEntries.get(0), + "TheRegistrar", + DateTime.parse("2000-02-19T00:00:00Z"), + true); + BillingEvent.OneTime expected = + defaultOneTimeBuilder() + .setCost(Money.of(USD, 4)) + .setParent(persistedEntries.get(0)) + .setCancellationMatchingBillingEvent(recurring.createVKey()) + .build(); + assertHistoryEntryMatches( + domain, + persistedEntries.get(1), + "TheRegistrar", + DateTime.parse("2000-05-20T00:00:00Z"), + true); + BillingEvent.OneTime expected2 = + defaultOneTimeBuilder() + .setCost(Money.of(USD, 4)) + .setBillingTime(DateTime.parse("2000-05-20T00:00:00Z")) + .setEventTime(DateTime.parse("2000-04-05T00:00:00Z")) + .setParent(persistedEntries.get(1)) + .setCancellationMatchingBillingEvent(recurring2.createVKey()) + .build(); + assertBillingEventsForResource(domain, expected, expected2, recurring, recurring2); + assertCursorAt(currentTestTime); + } + @TestOfyAndSql void testSuccess_premiumDomain() throws Exception { persistResource( @@ -749,6 +861,87 @@ public class ExpandRecurringBillingEventsActionTest { assertCursorAt(currentTestTime); } + @TestOfyAndSql + void testSuccess_premiumDomain_forAnchorTenant() throws Exception { + persistResource( + Registry.get("tld") + .asBuilder() + .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100")) + .build()); + recurring = persistResource(recurring.asBuilder().setRenewalPriceBehavior(NONPREMIUM).build()); + action.cursorTimeParam = Optional.of(START_OF_TIME); + runAction(); + DomainHistory persistedEntry = + getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); + BillingEvent.OneTime expected = + defaultOneTimeBuilder().setParent(persistedEntry).setCost(Money.of(USD, 11)).build(); + assertBillingEventsForResource(domain, expected, recurring); + assertCursorAt(currentTestTime); + } + + @TestOfyAndSql + void testSuccess_standardDomain_forAnchorTenant() throws Exception { + recurring = persistResource(recurring.asBuilder().setRenewalPriceBehavior(NONPREMIUM).build()); + action.cursorTimeParam = Optional.of(START_OF_TIME); + runAction(); + DomainHistory persistedEntry = + getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); + BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); + assertBillingEventsForResource(domain, expected, recurring); + assertCursorAt(currentTestTime); + } + + @TestOfyAndSql + void testSuccess_premiumDomain_forInternalRegistration() throws Exception { + persistResource( + Registry.get("tld") + .asBuilder() + .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100")) + .build()); + recurring = + persistResource( + recurring + .asBuilder() + .setRenewalPriceBehavior(SPECIFIED) + .setRenewalPrice(Money.of(USD, 20)) + .build()); + action.cursorTimeParam = Optional.of(START_OF_TIME); + runAction(); + DomainHistory persistedEntry = + getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); + BillingEvent.OneTime expected = + defaultOneTimeBuilder().setParent(persistedEntry).setCost(Money.of(USD, 20)).build(); + assertBillingEventsForResource(domain, expected, recurring); + assertCursorAt(currentTestTime); + } + + @TestOfyAndSql + void testSuccess_standardDomain_forInternalRegistration() throws Exception { + recurring = + persistResource( + recurring + .asBuilder() + .setRenewalPriceBehavior(SPECIFIED) + .setRenewalPrice(Money.of(USD, 2)) + .build()); + action.cursorTimeParam = Optional.of(START_OF_TIME); + runAction(); + DomainHistory persistedEntry = + getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); + BillingEvent.OneTime expected = + defaultOneTimeBuilder().setParent(persistedEntry).setCost(Money.of(USD, 2)).build(); + assertBillingEventsForResource(domain, expected, recurring); + assertCursorAt(currentTestTime); + } + @TestOfyAndSql void testSuccess_varyingRenewPrices() throws Exception { clock.setTo(currentTestTime); @@ -793,6 +986,102 @@ public class ExpandRecurringBillingEventsActionTest { assertCursorAt(currentTestTime); } + @TestOfyAndSql + void testSuccess_varyingRenewPrices_anchorTenant() throws Exception { + clock.setTo(currentTestTime); + persistResource( + Registry.get("tld") + .asBuilder() + .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100")) + .setRenewBillingCostTransitions( + ImmutableSortedMap.of( + START_OF_TIME, + Money.of(USD, 8), + DateTime.parse("2000-06-01T00:00:00Z"), + Money.of(USD, 10))) + .build()); + clock.setTo(DateTime.parse("2001-10-02T00:00:00Z")); + recurring = persistResource(recurring.asBuilder().setRenewalPriceBehavior(NONPREMIUM).build()); + action.cursorTimeParam = Optional.of(START_OF_TIME); + runAction(); + List persistedEntries = + getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); + assertThat(persistedEntries).hasSize(2); + DateTime eventDate = DateTime.parse("2000-01-05T00:00:00Z"); + DateTime billingDate = DateTime.parse("2000-02-19T00:00:00Z"); + assertHistoryEntryMatches(domain, persistedEntries.get(0), "TheRegistrar", billingDate, true); + BillingEvent.OneTime cheaper = + defaultOneTimeBuilder() + .setBillingTime(billingDate) + .setEventTime(eventDate) + .setParent(persistedEntries.get(0)) + .setCost(Money.of(USD, 8)) + .build(); + assertHistoryEntryMatches( + domain, persistedEntries.get(1), "TheRegistrar", billingDate.plusYears(1), true); + BillingEvent.OneTime expensive = + cheaper + .asBuilder() + .setCost(Money.of(USD, 10)) + .setBillingTime(billingDate.plusYears(1)) + .setEventTime(eventDate.plusYears(1)) + .setParent(persistedEntries.get(1)) + .build(); + assertBillingEventsForResource(domain, recurring, cheaper, expensive); + assertCursorAt(currentTestTime); + } + + @TestOfyAndSql + void testSuccess_varyingRenewPrices_internalRegistration() throws Exception { + clock.setTo(currentTestTime); + persistResource( + Registry.get("tld") + .asBuilder() + .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100")) + .setRenewBillingCostTransitions( + ImmutableSortedMap.of( + START_OF_TIME, + Money.of(USD, 8), + DateTime.parse("2000-06-01T00:00:00Z"), + Money.of(USD, 10))) + .build()); + clock.setTo(DateTime.parse("2001-10-02T00:00:00Z")); + recurring = + persistResource( + recurring + .asBuilder() + .setRenewalPriceBehavior(SPECIFIED) + .setRenewalPrice(Money.of(USD, 5)) + .build()); + action.cursorTimeParam = Optional.of(START_OF_TIME); + runAction(); + List persistedEntries = + getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); + assertThat(persistedEntries).hasSize(2); + DateTime eventDate = DateTime.parse("2000-01-05T00:00:00Z"); + DateTime billingDate = DateTime.parse("2000-02-19T00:00:00Z"); + assertHistoryEntryMatches(domain, persistedEntries.get(0), "TheRegistrar", billingDate, true); + BillingEvent.OneTime cheaper = + defaultOneTimeBuilder() + .setBillingTime(billingDate) + .setEventTime(eventDate) + .setParent(persistedEntries.get(0)) + .setCost(Money.of(USD, 5)) + .build(); + assertHistoryEntryMatches( + domain, persistedEntries.get(1), "TheRegistrar", billingDate.plusYears(1), true); + BillingEvent.OneTime expensive = + cheaper + .asBuilder() + .setCost(Money.of(USD, 5)) + .setBillingTime(billingDate.plusYears(1)) + .setEventTime(eventDate.plusYears(1)) + .setParent(persistedEntries.get(1)) + .build(); + assertBillingEventsForResource(domain, recurring, cheaper, expensive); + assertCursorAt(currentTestTime); + } + @TestOfyAndSql void testFailure_cursorAfterExecutionTime() { action.cursorTimeParam = Optional.of(clock.nowUtc().plusYears(1)); diff --git a/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java b/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java index 1b95b8dc9..f8a739e1d 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainPricingLogicTest.java @@ -64,7 +64,7 @@ import org.mockito.Mock; /** Unit tests for {@link DomainPricingLogic}. */ @DualDatabaseTest public class DomainPricingLogicTest { - DomainPricingLogic domainPricingLogic = new DomainPricingLogic(); + DomainPricingLogic domainPricingLogic; @RegisterExtension public final AppEngineExtension appEngine = @@ -81,8 +81,9 @@ public class DomainPricingLogicTest { void beforeEach() throws Exception { createTld("example"); sessionMetadata = new HttpSessionMetadata(new FakeHttpSession()); - domainPricingLogic.customLogic = - new DomainPricingCustomLogic(eppInput, sessionMetadata, flowMetadata); + domainPricingLogic = + new DomainPricingLogic( + new DomainPricingCustomLogic(eppInput, sessionMetadata, flowMetadata)); registry = persistResource( Registry.get("example")