From 7fb44e4f66fe559c82fe1c2608e2d7989fd516c1 Mon Sep 17 00:00:00 2001 From: larryruili Date: Thu, 24 Aug 2017 12:41:28 -0700 Subject: [PATCH] Parent recurring billing events under separate HistoryEntries To log autorenews, we currently run a mapreduce daily that creates synthetic billing events for each recurring event past its due time. These are all parented under the original recurring event, which allows these synthetic events to incorrectly stack on the original mutating entry. We now explicitly create a new HistoryEntry of type DOMAIN_AUTORENEW to log autorenews alongside other mutating EPP flows. These also parent DomainTransactionRecords for the NET_RENEWS_1_YEAR field, with the reporting time equal to the billing time (which accounts for the autorenew grace period). ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=166379700 --- .../ExpandRecurringBillingEventsAction.java | 41 +- .../model/reporting/HistoryEntry.java | 5 + .../registry/rdap/RdapJsonFormatter.java | 1 + .../tools/server/VerifyOteAction.java | 1 + ...xpandRecurringBillingEventsActionTest.java | 397 ++++++++---------- javatests/google/registry/model/schema.txt | 1 + 6 files changed, 216 insertions(+), 230 deletions(-) diff --git a/java/google/registry/batch/ExpandRecurringBillingEventsAction.java b/java/google/registry/batch/ExpandRecurringBillingEventsAction.java index 03e2862ff..0cede93f6 100644 --- a/java/google/registry/batch/ExpandRecurringBillingEventsAction.java +++ b/java/google/registry/batch/ExpandRecurringBillingEventsAction.java @@ -19,7 +19,9 @@ import static com.google.common.collect.Sets.difference; import static google.registry.mapreduce.MapreduceRunner.PARAM_DRY_RUN; import static google.registry.mapreduce.inputs.EppResourceInputs.createChildEntityInput; import static google.registry.model.common.Cursor.CursorType.RECURRING_BILLING; +import static google.registry.model.domain.Period.Unit.YEARS; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_AUTORENEW; import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost; import static google.registry.util.CollectionUtils.union; import static google.registry.util.DateTimeUtils.START_OF_TIME; @@ -42,13 +44,18 @@ import com.googlecode.objectify.VoidWork; import com.googlecode.objectify.Work; import google.registry.mapreduce.MapreduceRunner; import google.registry.mapreduce.inputs.NullInput; +import google.registry.model.ImmutableObject; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; import google.registry.model.billing.BillingEvent.OneTime; import google.registry.model.billing.BillingEvent.Recurring; import google.registry.model.common.Cursor; import google.registry.model.domain.DomainResource; +import google.registry.model.domain.Period; import google.registry.model.registry.Registry; +import google.registry.model.reporting.DomainTransactionRecord; +import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; +import google.registry.model.reporting.HistoryEntry; import google.registry.request.Action; import google.registry.request.Parameter; import google.registry.request.Response; @@ -59,6 +66,7 @@ import java.util.Set; import javax.inject.Inject; import org.joda.money.Money; import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; /** * A mapreduce that expands {@link Recurring} billing events into synthetic {@link OneTime} events. @@ -174,9 +182,32 @@ public class ExpandRecurringBillingEventsAction implements Runnable { ImmutableSet existingBillingTimes = getExistingBillingTimes(oneTimesForDomain, recurring); + ImmutableSet.Builder historyEntriesBuilder = + new ImmutableSet.Builder<>(); // Create synthetic OneTime events for all billing times that do not yet have an event // persisted. for (DateTime billingTime : difference(billingTimes, existingBillingTimes)) { + // Construct a new HistoryEntry that parents over the OneTime + HistoryEntry historyEntry = new HistoryEntry.Builder() + .setBySuperuser(false) + .setClientId(recurring.getClientId()) + .setModificationTime(DateTime.now(DateTimeZone.UTC)) + .setParent(recurring.getParentKey().getParent()) + .setPeriod(Period.create(1, YEARS)) + .setReason("Domain autorenewal by ExpandRecurringBillingEventsAction") + .setRequestedByRegistrar(false) + .setType(DOMAIN_AUTORENEW) + .setDomainTransactionRecords( + ImmutableSet.of( + DomainTransactionRecord.create( + tld.getTldStr(), + // We report this when the autorenew grace period ends + billingTime, + TransactionReportField.netRenewsFieldFromYears(1), + 1))) + .build(); + historyEntriesBuilder.add(historyEntry); + DateTime eventTime = billingTime.minus(tld.getAutoRenewGracePeriodLength()); // Determine the cost for a one-year renewal. Money renewCost = getDomainRenewCost(recurring.getTargetId(), eventTime, 1); @@ -186,7 +217,7 @@ public class ExpandRecurringBillingEventsAction implements Runnable { .setCost(renewCost) .setEventTime(eventTime) .setFlags(union(recurring.getFlags(), Flag.SYNTHETIC)) - .setParent(recurring.getParentKey()) + .setParent(historyEntry) .setPeriodYears(1) .setReason(recurring.getReason()) .setSyntheticCreationTime(executeTime) @@ -194,9 +225,15 @@ public class ExpandRecurringBillingEventsAction implements Runnable { .setTargetId(recurring.getTargetId()) .build()); } + Set historyEntries = historyEntriesBuilder.build(); Set syntheticOneTimes = syntheticOneTimesBuilder.build(); if (!isDryRun) { - ofy().save().entities(syntheticOneTimes).now(); + ImmutableSet entitiesToSave = + new ImmutableSet.Builder() + .addAll(historyEntries) + .addAll(syntheticOneTimes) + .build(); + ofy().save().entities(entitiesToSave).now(); } return syntheticOneTimes.size(); } diff --git a/java/google/registry/model/reporting/HistoryEntry.java b/java/google/registry/model/reporting/HistoryEntry.java index 2bd02e3dc..2301dcc2b 100644 --- a/java/google/registry/model/reporting/HistoryEntry.java +++ b/java/google/registry/model/reporting/HistoryEntry.java @@ -54,6 +54,11 @@ public class HistoryEntry extends ImmutableObject implements Buildable { DOMAIN_APPLICATION_DELETE, DOMAIN_APPLICATION_UPDATE, DOMAIN_APPLICATION_STATUS_UPDATE, + /** + * Used for domain registration autorenews explicitly logged by + * {@link google.registry.batch.ExpandRecurringBillingEventsAction}. + */ + DOMAIN_AUTORENEW, DOMAIN_CREATE, DOMAIN_DELETE, DOMAIN_RENEW, diff --git a/java/google/registry/rdap/RdapJsonFormatter.java b/java/google/registry/rdap/RdapJsonFormatter.java index fc30f323d..4ef16f502 100644 --- a/java/google/registry/rdap/RdapJsonFormatter.java +++ b/java/google/registry/rdap/RdapJsonFormatter.java @@ -255,6 +255,7 @@ public class RdapJsonFormatter { .put(HistoryEntry.Type.CONTACT_TRANSFER_APPROVE, RdapEventAction.TRANSFER) .put(HistoryEntry.Type.DOMAIN_APPLICATION_CREATE, RdapEventAction.REGISTRATION) .put(HistoryEntry.Type.DOMAIN_APPLICATION_DELETE, RdapEventAction.DELETION) + .put(HistoryEntry.Type.DOMAIN_AUTORENEW, RdapEventAction.REREGISTRATION) .put(HistoryEntry.Type.DOMAIN_CREATE, RdapEventAction.REGISTRATION) .put(HistoryEntry.Type.DOMAIN_DELETE, RdapEventAction.DELETION) .put(HistoryEntry.Type.DOMAIN_RENEW, RdapEventAction.REREGISTRATION) diff --git a/java/google/registry/tools/server/VerifyOteAction.java b/java/google/registry/tools/server/VerifyOteAction.java index 22437a925..9e4eb27a6 100644 --- a/java/google/registry/tools/server/VerifyOteAction.java +++ b/java/google/registry/tools/server/VerifyOteAction.java @@ -187,6 +187,7 @@ public class VerifyOteAction implements Runnable, JsonAction { DOMAIN_APPLICATION_CREATES_SUNRISE(1, equalTo(Type.DOMAIN_APPLICATION_CREATE), IS_SUNRISE), DOMAIN_APPLICATION_DELETES(2, equalTo(Type.DOMAIN_APPLICATION_DELETE)), DOMAIN_APPLICATION_UPDATES(2, equalTo(Type.DOMAIN_APPLICATION_UPDATE)), + DOMAIN_AUTORENEWS(0, equalTo(Type.DOMAIN_AUTORENEW)), DOMAIN_CREATES(0, equalTo(Type.DOMAIN_CREATE)), DOMAIN_CREATES_ASCII(1, equalTo(Type.DOMAIN_CREATE), not(IS_IDN)), DOMAIN_CREATES_IDN(1, equalTo(Type.DOMAIN_CREATE), IS_IDN), diff --git a/javatests/google/registry/batch/ExpandRecurringBillingEventsActionTest.java b/javatests/google/registry/batch/ExpandRecurringBillingEventsActionTest.java index 6670dd87c..5d0e16f50 100644 --- a/javatests/google/registry/batch/ExpandRecurringBillingEventsActionTest.java +++ b/javatests/google/registry/batch/ExpandRecurringBillingEventsActionTest.java @@ -16,10 +16,14 @@ package google.registry.batch; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.common.Cursor.CursorType.RECURRING_BILLING; +import static google.registry.model.domain.Period.Unit.YEARS; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_AUTORENEW; import static google.registry.testing.DatastoreHelper.assertBillingEvents; import static google.registry.testing.DatastoreHelper.assertBillingEventsForResource; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.getHistoryEntriesOfType; +import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType; import static google.registry.testing.DatastoreHelper.persistActiveDomain; import static google.registry.testing.DatastoreHelper.persistDeletedDomain; import static google.registry.testing.DatastoreHelper.persistPremiumList; @@ -36,11 +40,15 @@ import com.googlecode.objectify.Key; import com.googlecode.objectify.VoidWork; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; +import google.registry.model.billing.BillingEvent.OneTime; import google.registry.model.billing.BillingEvent.Reason; import google.registry.model.common.Cursor; import google.registry.model.domain.DomainResource; +import google.registry.model.domain.Period; import google.registry.model.ofy.Ofy; import google.registry.model.registry.Registry; +import google.registry.model.reporting.DomainTransactionRecord; +import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; import google.registry.model.reporting.HistoryEntry; import google.registry.testing.ExceptionRule; import google.registry.testing.FakeClock; @@ -118,24 +126,49 @@ public class ExpandRecurringBillingEventsActionTest assertThat(cursor.getCursorTime()).isEqualTo(expectedCursorTime); } - @Test - public void testSuccess_expandSingleEvent() throws Exception { - persistResource(recurring); - action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. + void assertHistoryEntryMatches( + DomainResource domain, HistoryEntry actual, String clientId, DateTime billingTime) { + assertThat(actual.getBySuperuser()).isFalse(); + assertThat(actual.getClientId()).isEqualTo(clientId); + assertThat(actual.getParent()).isEqualTo(Key.create(domain)); + assertThat(actual.getPeriod()).isEqualTo(Period.create(1, YEARS)); + assertThat(actual.getReason()) + .isEqualTo("Domain autorenewal by ExpandRecurringBillingEventsAction"); + assertThat(actual.getRequestedByRegistrar()).isFalse(); + assertThat(actual.getType()).isEqualTo(DOMAIN_AUTORENEW); + assertThat(actual.getDomainTransactionRecords()) + .containsExactly( + DomainTransactionRecord.create( + "tld", + billingTime, + TransactionReportField.NET_RENEWS_1_YR, + 1)); + } + + private OneTime.Builder defaultOneTimeBuilder() { + return new BillingEvent.OneTime.Builder() .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) .setClientId("TheRegistrar") .setCost(Money.of(USD, 11)) .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) .setPeriodYears(1) .setReason(Reason.RENEW) .setSyntheticCreationTime(beginningOfTest) .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) + .setTargetId(domain.getFullyQualifiedDomainName()); + } + + @Test + public void testSuccess_expandSingleEvent() throws Exception { + persistResource(recurring); + action.cursorTimeParam = Optional.of(START_OF_TIME); + runMapreduce(); + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder() + .setParent(persistedEntry) .build(); assertBillingEventsForResource(domain, expected, recurring); assertCursorAt(beginningOfTest); @@ -158,18 +191,11 @@ public class ExpandRecurringBillingEventsActionTest .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) - .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) - .setCancellationMatchingBillingEvent(Key.create(recurring)) + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(deletedDomain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + deletedDomain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder() + .setParent(persistedEntry) .setTargetId(deletedDomain.getFullyQualifiedDomainName()) .build(); assertBillingEventsForResource(deletedDomain, expected, recurring); @@ -181,20 +207,10 @@ public class ExpandRecurringBillingEventsActionTest persistResource(recurring); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) - .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) - .build(); + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertCursorAt(beginningOfTest); DateTime beginningOfSecondRun = clock.nowUtc(); action.response = new FakeResponse(); @@ -206,50 +222,33 @@ public class ExpandRecurringBillingEventsActionTest @Test public void testSuccess_expandSingleEvent_idempotentForExistingOneTime() throws Exception { persistResource(recurring); - BillingEvent.OneTime persisted = persistResource(new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) - .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) + BillingEvent.OneTime persisted = persistResource(defaultOneTimeBuilder() .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); + // No new history entries should be generated + assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertCursorAt(beginningOfTest); - assertBillingEventsForResource(domain, persisted, recurring); // no additional billing events + // No additional billing events should be generated + assertBillingEventsForResource(domain, persisted, recurring); } @Test public void testSuccess_expandSingleEvent_notIdempotentForDifferentBillingTime() throws Exception { persistResource(recurring); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) - .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) - .build(); + action.cursorTimeParam = Optional.of(START_OF_TIME); + runMapreduce(); + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); // Persist an otherwise identical billing event that differs only in billing time. BillingEvent.OneTime persisted = persistResource(expected.asBuilder() .setBillingTime(DateTime.parse("1999-02-19T00:00:00Z")) .setEventTime(DateTime.parse("1999-01-05T00:00:00Z")) .build()); - action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); assertCursorAt(beginningOfTest); assertBillingEventsForResource(domain, persisted, expected, recurring); } @@ -261,25 +260,23 @@ public class ExpandRecurringBillingEventsActionTest BillingEvent.Recurring recurring2 = persistResource(recurring.asBuilder() .setId(3L) .build()); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) - .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) - .build(); - // Persist an otherwise identical billing event that differs only in recurring event key. - BillingEvent.OneTime persisted = persistResource(expected.asBuilder() - .setCancellationMatchingBillingEvent(Key.create(recurring2)) - .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); + List persistedEntries = + getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW); + for (HistoryEntry persistedEntry : persistedEntries) { + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + } + assertThat(persistedEntries).hasSize(2); + BillingEvent.OneTime expected = defaultOneTimeBuilder() + .setParent(persistedEntries.get(0)) + .build(); + // Persist an otherwise identical billing event that differs only in recurring event key. + BillingEvent.OneTime persisted = expected.asBuilder() + .setParent(persistedEntries.get(1)) + .setCancellationMatchingBillingEvent(Key.create(recurring2)) + .build(); assertCursorAt(beginningOfTest); assertBillingEventsForResource(domain, persisted, expected, recurring, recurring2); } @@ -292,6 +289,8 @@ public class ExpandRecurringBillingEventsActionTest .build()); action.cursorTimeParam = Optional.of(DateTime.parse("2000-01-01T00:00:00Z")); runMapreduce(); + // No new history entries should be generated + assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); assertCursorAt(beginningOfTest); } @@ -303,6 +302,8 @@ public class ExpandRecurringBillingEventsActionTest .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); + // No new history entries should be generated + assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); } @@ -311,20 +312,10 @@ public class ExpandRecurringBillingEventsActionTest persistResource(recurring); action.cursorTimeParam = Optional.of(DateTime.parse("2000-02-19T00:00:00Z")); runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) - .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) - .build(); + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertBillingEventsForResource(domain, expected, recurring); assertCursorAt(beginningOfTest); } @@ -335,20 +326,10 @@ public class ExpandRecurringBillingEventsActionTest persistResource(recurring); action.cursorTimeParam = Optional.of(DateTime.parse("2000-01-12T00:00:00Z")); runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) - .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) - .build(); + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertBillingEventsForResource(domain, expected, recurring); assertCursorAt(beginningOfTest); } @@ -361,6 +342,8 @@ public class ExpandRecurringBillingEventsActionTest // Clock is advanced one milli in runMapreduce() clock.setTo(testTime); runMapreduce(); + // No new history entries should be generated + assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); // A candidate billing event is set to be billed exactly on 2/19/00 @ 00:00, // but these should not be generated as the interval is closed on cursorTime, open on // executeTime. @@ -377,19 +360,14 @@ public class ExpandRecurringBillingEventsActionTest recurring.asBuilder().setEventTime(recurring.getEventTime().plusYears(2)).build()); clock.setTo(testTime); runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2002-02-19T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder() .setBillingTime(DateTime.parse("2002-02-19T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) .setEventTime(DateTime.parse("2002-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) + .setParent(persistedEntry) .setSyntheticCreationTime(testTime) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) .build(); assertBillingEventsForResource(domain, expected, recurring); assertCursorAt(testTime); @@ -400,20 +378,10 @@ public class ExpandRecurringBillingEventsActionTest persistResource(recurring); saveCursor(START_OF_TIME); runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) - .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) - .build(); + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertBillingEventsForResource(domain, expected, recurring); assertCursorAt(beginningOfTest); } @@ -424,6 +392,8 @@ public class ExpandRecurringBillingEventsActionTest // Simulate a quick second run of the mapreduce (this should be a no-op). saveCursor(clock.nowUtc().minusSeconds(1)); runMapreduce(); + // No new history entries should be generated + assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); assertCursorAt(beginningOfTest); } @@ -436,6 +406,8 @@ public class ExpandRecurringBillingEventsActionTest .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); + // No new history entries should be generated + assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); assertCursorAt(beginningOfTest); } @@ -446,6 +418,8 @@ public class ExpandRecurringBillingEventsActionTest action.isDryRun = true; saveCursor(START_OF_TIME); // Need a saved cursor to verify that it didn't move. runMapreduce(); + // No new history entries should be generated + assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); assertCursorAt(START_OF_TIME); // Cursor doesn't move on a dry run. } @@ -458,23 +432,23 @@ public class ExpandRecurringBillingEventsActionTest expectedEvents.add(persistResource(recurring)); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); + List persistedEntries = + getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW); + assertThat(persistedEntries).hasSize(6); DateTime eventDate = DateTime.parse("2000-01-05T00:00:00Z"); - // Default renew grace period of 45 days. DateTime billingDate = DateTime.parse("2000-02-19T00:00:00Z"); // Expecting events for '00, '01, '02, '03, '04, '05. for (int year = 0; year < 6; year++) { - expectedEvents.add(new BillingEvent.OneTime.Builder() + assertHistoryEntryMatches( + domain, + persistedEntries.get(year), + "TheRegistrar", + billingDate.plusYears(year)); + expectedEvents.add(defaultOneTimeBuilder() .setBillingTime(billingDate.plusYears(year)) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) .setEventTime(eventDate.plusYears(year)) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) + .setParent(persistedEntries.get(year)) .setSyntheticCreationTime(testTime) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) .build()); } assertBillingEventsForResource(domain, Iterables.toArray(expectedEvents, BillingEvent.class)); @@ -489,26 +463,24 @@ public class ExpandRecurringBillingEventsActionTest expectedEvents.add(persistResource(recurring)); saveCursor(DateTime.parse("2003-10-02T00:00:00Z")); runMapreduce(); + List persistedEntries = + getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW); + assertThat(persistedEntries).hasSize(2); DateTime eventDate = DateTime.parse("2004-01-05T00:00:00Z"); - // Default renew grace period of 45 days. DateTime billingDate = DateTime.parse("2004-02-19T00:00:00Z"); // Only expect the last two years' worth of billing events. for (int year = 0; year < 2; year++) { - expectedEvents.add(new BillingEvent.OneTime.Builder() + assertHistoryEntryMatches( + domain, persistedEntries.get(year), "TheRegistrar", billingDate.plusYears(year)); + expectedEvents.add(defaultOneTimeBuilder() .setBillingTime(billingDate.plusYears(year)) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) + .setParent(persistedEntries.get(year)) .setEventTime(eventDate.plusYears(year)) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) .setSyntheticCreationTime(testTime) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) .build()); } - assertBillingEventsForResource(domain, Iterables.toArray(expectedEvents, BillingEvent.class)); + assertBillingEventsForResource( + domain, Iterables.toArray(expectedEvents, BillingEvent.class)); assertCursorAt(testTime); } @@ -519,6 +491,8 @@ public class ExpandRecurringBillingEventsActionTest persistResource(recurring); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); + // No new history entries should be generated + assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); assertCursorAt(testTime); } @@ -534,18 +508,13 @@ public class ExpandRecurringBillingEventsActionTest .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder() .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) - .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) + .setParent(persistedEntry) .setSyntheticCreationTime(testTime) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) .build(); assertBillingEventsForResource(domain, recurring, expected); assertCursorAt(testTime); @@ -558,19 +527,13 @@ public class ExpandRecurringBillingEventsActionTest recurring.asBuilder().setEventTime(DateTime.parse("2000-01-15T00:00:00Z")).build()); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-29T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder() .setBillingTime(DateTime.parse("2000-02-29T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) .setEventTime(DateTime.parse("2000-01-15T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) + .setParent(persistedEntry) .build(); assertBillingEventsForResource(domain, expected, recurring); assertCursorAt(beginningOfTest); @@ -585,19 +548,14 @@ public class ExpandRecurringBillingEventsActionTest action.cursorTimeParam = Optional.of(START_OF_TIME); clock.setTo(testTime); runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2001-03-01T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder() .setBillingTime(DateTime.parse("2001-03-01T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) .setEventTime(DateTime.parse("2001-01-15T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) + .setParent(persistedEntry) .setSyntheticCreationTime(testTime) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) .build(); assertBillingEventsForResource(domain, expected, recurring); assertCursorAt(testTime); @@ -612,33 +570,21 @@ public class ExpandRecurringBillingEventsActionTest .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) - .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) + List persistedEntries = getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW); + assertThat(persistedEntries).hasSize(2); + assertHistoryEntryMatches( + domain, persistedEntries.get(0), "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder() + .setParent(persistedEntries.get(0)) .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) .build(); - BillingEvent.OneTime expected2 = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. + assertHistoryEntryMatches( + domain, persistedEntries.get(1), "TheRegistrar", DateTime.parse("2000-05-20T00:00:00Z")); + BillingEvent.OneTime expected2 = defaultOneTimeBuilder() .setBillingTime(DateTime.parse("2000-05-20T00:00:00Z")) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 11)) .setEventTime(DateTime.parse("2000-04-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) + .setParent(persistedEntries.get(1)) .setCancellationMatchingBillingEvent(Key.create(recurring2)) - .setTargetId(domain.getFullyQualifiedDomainName()) .build(); assertBillingEventsForResource(domain, expected, expected2, recurring, recurring2); assertCursorAt(beginningOfTest); @@ -654,19 +600,12 @@ public class ExpandRecurringBillingEventsActionTest persistResource(recurring); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); - BillingEvent.OneTime expected = new BillingEvent.OneTime.Builder() - // Default renew grace period of 45 days. - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setClientId("TheRegistrar") + HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); + assertHistoryEntryMatches( + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + BillingEvent.OneTime expected = defaultOneTimeBuilder() + .setParent(persistedEntry) .setCost(Money.of(USD, 100)) - .setEventTime(DateTime.parse("2000-01-05T00:00:00Z")) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) .build(); assertBillingEventsForResource(domain, expected, recurring); assertCursorAt(beginningOfTest); @@ -687,26 +626,26 @@ public class ExpandRecurringBillingEventsActionTest persistResource(recurring); action.cursorTimeParam = Optional.of(START_OF_TIME); runMapreduce(); + List persistedEntries = + getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW); + assertThat(persistedEntries).hasSize(2); DateTime eventDate = DateTime.parse("2000-01-05T00:00:00Z"); - // Default renew grace period of 45 days. DateTime billingDate = DateTime.parse("2000-02-19T00:00:00Z"); - BillingEvent.OneTime cheaper = new BillingEvent.OneTime.Builder() + assertHistoryEntryMatches(domain, persistedEntries.get(0), "TheRegistrar", billingDate); + BillingEvent.OneTime cheaper = defaultOneTimeBuilder() .setBillingTime(billingDate) - .setClientId("TheRegistrar") - .setCost(Money.of(USD, 8)) .setEventTime(eventDate) - .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(Reason.RENEW) + .setParent(persistedEntries.get(0)) + .setCost(Money.of(USD, 8)) .setSyntheticCreationTime(testTime) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(domain.getFullyQualifiedDomainName()) .build(); + assertHistoryEntryMatches( + domain, persistedEntries.get(1), "TheRegistrar", billingDate.plusYears(1)); 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(testTime); @@ -735,6 +674,8 @@ public class ExpandRecurringBillingEventsActionTest // Set target to a TLD that doesn't exist. recurring = persistResource(recurring.asBuilder().setTargetId("domain.junk").build()); runMapreduce(); + // No new history entries should be generated + assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEvents(recurring); // only the bogus one in Datastore assertCursorAt(START_OF_TIME); // Cursor doesn't move on a failure. } diff --git a/javatests/google/registry/model/schema.txt b/javatests/google/registry/model/schema.txt index 1b36337e4..15b53a213 100644 --- a/javatests/google/registry/model/schema.txt +++ b/javatests/google/registry/model/schema.txt @@ -846,6 +846,7 @@ enum google.registry.model.reporting.HistoryEntry$Type { DOMAIN_APPLICATION_DELETE; DOMAIN_APPLICATION_STATUS_UPDATE; DOMAIN_APPLICATION_UPDATE; + DOMAIN_AUTORENEW; DOMAIN_CREATE; DOMAIN_DELETE; DOMAIN_RENEW;