diff --git a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java index b18ebdcb8..4c36782e9 100644 --- a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java +++ b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java @@ -190,14 +190,18 @@ public class ExpandRecurringBillingEventsAction implements Runnable { .setReason("Domain autorenewal by ExpandRecurringBillingEventsAction") .setRequestedByRegistrar(false) .setType(DOMAIN_AUTORENEW) + // Don't write a domain transaction record if the recurrence was ended prior to the + // billing time (i.e. a domain was deleted during the autorenew grace period). .setDomainTransactionRecords( - ImmutableSet.of( - DomainTransactionRecord.create( - tld.getTldStr(), - // We report this when the autorenew grace period ends - billingTime, - TransactionReportField.netRenewsFieldFromYears(1), - 1))) + recurring.getRecurrenceEndTime().isBefore(billingTime) + ? ImmutableSet.of() + : ImmutableSet.of( + DomainTransactionRecord.create( + tld.getTldStr(), + // We report this when the autorenew grace period ends + billingTime, + TransactionReportField.netRenewsFieldFromYears(1), + 1))) .build(); historyEntriesBuilder.add(historyEntry); diff --git a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java index ce77a7fa1..5a3d75276 100644 --- a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java +++ b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java @@ -25,7 +25,7 @@ import static google.registry.testing.DatastoreHelper.assertBillingEventsForReso 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.newDomainBase; import static google.registry.testing.DatastoreHelper.persistDeletedDomain; import static google.registry.testing.DatastoreHelper.persistPremiumList; import static google.registry.testing.DatastoreHelper.persistResource; @@ -87,7 +87,8 @@ public class ExpandRecurringBillingEventsActionTest action.clock = clock; action.cursorTimeParam = Optional.empty(); createTld("tld"); - domain = persistActiveDomain("example.tld"); + domain = persistResource(newDomainBase("example.tld").asBuilder() + .setCreationTimeForTest(DateTime.parse("1999-01-05T00:00:00Z")).build()); historyEntry = persistResource(new HistoryEntry.Builder().setParent(domain).build()); recurring = new BillingEvent.Recurring.Builder() .setParent(historyEntry) @@ -101,25 +102,26 @@ public class ExpandRecurringBillingEventsActionTest .build(); } - void saveCursor(final DateTime cursorTime) { + private void saveCursor(final DateTime cursorTime) { tm().transact(() -> ofy().save().entity(Cursor.createGlobal(RECURRING_BILLING, cursorTime))); } - void runMapreduce() throws Exception { + private void runMapreduce() throws Exception { action.response = new FakeResponse(); action.run(); executeTasksUntilEmpty("mapreduce", clock); ofy().clearSessionCache(); } - void assertCursorAt(DateTime expectedCursorTime) { + private void assertCursorAt(DateTime expectedCursorTime) { Cursor cursor = ofy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now(); assertThat(cursor).isNotNull(); assertThat(cursor.getCursorTime()).isEqualTo(expectedCursorTime); } - void assertHistoryEntryMatches( - DomainBase domain, HistoryEntry actual, String clientId, DateTime billingTime) { + private void assertHistoryEntryMatches( + DomainBase domain, HistoryEntry actual, String clientId, DateTime billingTime, + boolean shouldHaveTxRecord) { assertThat(actual.getBySuperuser()).isFalse(); assertThat(actual.getClientId()).isEqualTo(clientId); assertThat(actual.getParent()).isEqualTo(Key.create(domain)); @@ -128,13 +130,17 @@ public class ExpandRecurringBillingEventsActionTest .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)); + if (shouldHaveTxRecord) { + assertThat(actual.getDomainTransactionRecords()) + .containsExactly( + DomainTransactionRecord.create( + "tld", + billingTime, + TransactionReportField.NET_RENEWS_1_YR, + 1)); + } else { + assertThat(actual.getDomainTransactionRecords()).isEmpty(); + } } private OneTime.Builder defaultOneTimeBuilder() { @@ -158,7 +164,7 @@ public class ExpandRecurringBillingEventsActionTest runMapreduce(); HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); assertHistoryEntryMatches( - domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder() .setParent(persistedEntry) .build(); @@ -185,7 +191,8 @@ public class ExpandRecurringBillingEventsActionTest runMapreduce(); HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(deletedDomain, DOMAIN_AUTORENEW); assertHistoryEntryMatches( - deletedDomain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + deletedDomain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), + true); BillingEvent.OneTime expected = defaultOneTimeBuilder() .setParent(persistedEntry) .setTargetId(deletedDomain.getFullyQualifiedDomainName()) @@ -201,7 +208,7 @@ public class ExpandRecurringBillingEventsActionTest runMapreduce(); HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); assertHistoryEntryMatches( - domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertCursorAt(beginningOfTest); DateTime beginningOfSecondRun = clock.nowUtc(); @@ -234,7 +241,7 @@ public class ExpandRecurringBillingEventsActionTest runMapreduce(); HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); assertHistoryEntryMatches( - domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); 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() @@ -258,7 +265,7 @@ public class ExpandRecurringBillingEventsActionTest getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW); for (HistoryEntry persistedEntry : persistedEntries) { assertHistoryEntryMatches( - domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); } assertThat(persistedEntries).hasSize(2); BillingEvent.OneTime expected = defaultOneTimeBuilder() @@ -306,7 +313,7 @@ public class ExpandRecurringBillingEventsActionTest runMapreduce(); HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); assertHistoryEntryMatches( - domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertBillingEventsForResource(domain, expected, recurring); assertCursorAt(beginningOfTest); @@ -320,7 +327,7 @@ public class ExpandRecurringBillingEventsActionTest runMapreduce(); HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); assertHistoryEntryMatches( - domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertBillingEventsForResource(domain, expected, recurring); assertCursorAt(beginningOfTest); @@ -354,7 +361,7 @@ public class ExpandRecurringBillingEventsActionTest runMapreduce(); HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); assertHistoryEntryMatches( - domain, persistedEntry, "TheRegistrar", DateTime.parse("2002-02-19T00:00:00Z")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2002-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder() .setBillingTime(DateTime.parse("2002-02-19T00:00:00Z")) .setEventTime(DateTime.parse("2002-01-05T00:00:00Z")) @@ -372,7 +379,7 @@ public class ExpandRecurringBillingEventsActionTest runMapreduce(); HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); assertHistoryEntryMatches( - domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertBillingEventsForResource(domain, expected, recurring); assertCursorAt(beginningOfTest); @@ -435,7 +442,7 @@ public class ExpandRecurringBillingEventsActionTest domain, persistedEntries.get(year), "TheRegistrar", - billingDate.plusYears(year)); + billingDate.plusYears(year), true); expectedEvents.add(defaultOneTimeBuilder() .setBillingTime(billingDate.plusYears(year)) .setEventTime(eventDate.plusYears(year)) @@ -463,7 +470,7 @@ public class ExpandRecurringBillingEventsActionTest // Only expect the last two years' worth of billing events. for (int year = 0; year < 2; year++) { assertHistoryEntryMatches( - domain, persistedEntries.get(year), "TheRegistrar", billingDate.plusYears(year)); + domain, persistedEntries.get(year), "TheRegistrar", billingDate.plusYears(year), true); expectedEvents.add(defaultOneTimeBuilder() .setBillingTime(billingDate.plusYears(year)) .setParent(persistedEntries.get(year)) @@ -490,19 +497,47 @@ public class ExpandRecurringBillingEventsActionTest } @Test - public void testSuccess_singleEvent_afterRecurrenceEnd() throws Exception { - DateTime testTime = beginningOfTest.plusYears(2); + public void testSuccess_singleEvent_afterRecurrenceEnd_inAutorenewGracePeriod() throws Exception { + // The domain creation date is 1999-01-05, and the first renewal date is thus 2000-01-05. + DateTime testTime = DateTime.parse("2001-02-06T00:00:00Z"); clock.setTo(testTime); recurring = persistResource(recurring.asBuilder() - // Set between event time and billing time (i.e. before the grace period expires) for 2000. - // We should still expect a billing event. + // The domain deletion date is 2000-01-29, which is within the 45 day autorenew grace period + // from the renewal date. .setRecurrenceEndTime(DateTime.parse("2000-01-29T00:00:00Z")) + .setEventTime(domain.getCreationTime().plusYears(1)) .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")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), false); + BillingEvent.OneTime expected = defaultOneTimeBuilder() + .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) + .setParent(persistedEntry) + .setSyntheticCreationTime(testTime) + .build(); + assertBillingEventsForResource(domain, recurring, expected); + assertCursorAt(testTime); + } + + @Test + public void testSuccess_singleEvent_afterRecurrenceEnd_outsideAutorenewGracePeriod() + throws Exception { + // The domain creation date is 1999-01-05, and the first renewal date is thus 2000-01-05. + DateTime testTime = DateTime.parse("2001-02-06T00:00:00Z"); + clock.setTo(testTime); + recurring = persistResource(recurring.asBuilder() + // The domain deletion date is 2000-04-05, which is not within the 45 day autorenew grace + // period from the renewal date. + .setRecurrenceEndTime(DateTime.parse("2000-04-05T00:00:00Z")) + .setEventTime(domain.getCreationTime().plusYears(1)) + .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"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder() .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) .setParent(persistedEntry) @@ -521,7 +556,7 @@ public class ExpandRecurringBillingEventsActionTest runMapreduce(); HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); assertHistoryEntryMatches( - domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-29T00:00:00Z")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-29T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder() .setBillingTime(DateTime.parse("2000-02-29T00:00:00Z")) .setEventTime(DateTime.parse("2000-01-15T00:00:00Z")) @@ -542,7 +577,7 @@ public class ExpandRecurringBillingEventsActionTest runMapreduce(); HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); assertHistoryEntryMatches( - domain, persistedEntry, "TheRegistrar", DateTime.parse("2001-03-01T00:00:00Z")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2001-03-01T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder() .setBillingTime(DateTime.parse("2001-03-01T00:00:00Z")) .setEventTime(DateTime.parse("2001-01-15T00:00:00Z")) @@ -565,13 +600,15 @@ public class ExpandRecurringBillingEventsActionTest List persistedEntries = getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW); assertThat(persistedEntries).hasSize(2); assertHistoryEntryMatches( - domain, persistedEntries.get(0), "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + domain, persistedEntries.get(0), "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), + true); BillingEvent.OneTime expected = defaultOneTimeBuilder() .setParent(persistedEntries.get(0)) .setCancellationMatchingBillingEvent(Key.create(recurring)) .build(); assertHistoryEntryMatches( - domain, persistedEntries.get(1), "TheRegistrar", DateTime.parse("2000-05-20T00:00:00Z")); + 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")) @@ -594,7 +631,7 @@ public class ExpandRecurringBillingEventsActionTest runMapreduce(); HistoryEntry persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW); assertHistoryEntryMatches( - domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z")); + domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder() .setParent(persistedEntry) .setCost(Money.of(USD, 100)) @@ -623,7 +660,7 @@ public class ExpandRecurringBillingEventsActionTest 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); + assertHistoryEntryMatches(domain, persistedEntries.get(0), "TheRegistrar", billingDate, true); BillingEvent.OneTime cheaper = defaultOneTimeBuilder() .setBillingTime(billingDate) .setEventTime(eventDate) @@ -632,7 +669,7 @@ public class ExpandRecurringBillingEventsActionTest .setSyntheticCreationTime(testTime) .build(); assertHistoryEntryMatches( - domain, persistedEntries.get(1), "TheRegistrar", billingDate.plusYears(1)); + domain, persistedEntries.get(1), "TheRegistrar", billingDate.plusYears(1), true); BillingEvent.OneTime expensive = cheaper.asBuilder() .setCost(Money.of(USD, 10)) .setBillingTime(billingDate.plusYears(1))