diff --git a/java/google/registry/flows/domain/DomainAllocateFlow.java b/java/google/registry/flows/domain/DomainAllocateFlow.java index ef6099b55..e59967e6d 100644 --- a/java/google/registry/flows/domain/DomainAllocateFlow.java +++ b/java/google/registry/flows/domain/DomainAllocateFlow.java @@ -32,6 +32,7 @@ import static google.registry.model.EppResourceUtils.createDomainRepoId; import static google.registry.model.EppResourceUtils.loadDomainApplication; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation; +import static google.registry.model.reporting.DomainTransactionRecord.TransactionFieldAmount.TransactionReportField.netAddsFieldFromYears; import static google.registry.pricing.PricingEngineProxy.getDomainCreateCost; import static google.registry.util.CollectionUtils.isNullOrEmpty; import static google.registry.util.DateTimeUtils.END_OF_TIME; @@ -85,12 +86,15 @@ import google.registry.model.poll.PendingActionNotificationResponse.DomainPendin import google.registry.model.poll.PollMessage; import google.registry.model.registry.Registry; import google.registry.model.registry.label.ReservationType; +import google.registry.model.reporting.DomainTransactionRecord; +import google.registry.model.reporting.DomainTransactionRecord.TransactionFieldAmount; import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.IcannReportingTypes.ActivityReportField; import google.registry.tmch.LordnTask; import java.util.Set; import javax.inject.Inject; import org.joda.time.DateTime; +import org.joda.time.Duration; /** * An EPP flow that allocates a new domain resource from a domain application. @@ -156,7 +160,8 @@ public class DomainAllocateFlow implements TransactionalFlow { loadAndValidateApplication(allocateCreate.getApplicationRoid(), now); String repoId = createDomainRepoId(ObjectifyService.allocateId(), registry.getTldStr()); ImmutableSet.Builder entitiesToSave = new ImmutableSet.Builder<>(); - HistoryEntry historyEntry = buildHistoryEntry(repoId, period, now); + HistoryEntry historyEntry = buildHistoryEntry( + repoId, period, now, registry.getAddGracePeriodLength(), registry.getTldStr()); entitiesToSave.add(historyEntry); ImmutableSet billsAndPolls = createBillingEventsAndPollMessages( domainName, application, historyEntry, isSunrushAddGracePeriod, registry, now, years); @@ -229,12 +234,18 @@ public class DomainAllocateFlow implements TransactionalFlow { return application; } - private HistoryEntry buildHistoryEntry(String repoId, Period period, DateTime now) { + private HistoryEntry buildHistoryEntry( + String repoId, Period period, DateTime now, Duration addGracePeriod, String tld) { return historyBuilder .setType(HistoryEntry.Type.DOMAIN_ALLOCATE) .setPeriod(period) .setModificationTime(now) .setParent(Key.create(DomainResource.class, repoId)) + .setDomainTransactionRecord( + DomainTransactionRecord.create( + tld, + now.plus(addGracePeriod), + TransactionFieldAmount.create(netAddsFieldFromYears(period.getValue()), 1))) .build(); } diff --git a/java/google/registry/flows/domain/DomainCreateFlow.java b/java/google/registry/flows/domain/DomainCreateFlow.java index c83c97e37..333ce9665 100644 --- a/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/java/google/registry/flows/domain/DomainCreateFlow.java @@ -42,6 +42,7 @@ import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBIT import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation; +import static google.registry.model.reporting.DomainTransactionRecord.TransactionFieldAmount.TransactionReportField.netAddsFieldFromYears; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.leapSafeAddYears; @@ -98,11 +99,14 @@ import google.registry.model.poll.PollMessage; import google.registry.model.poll.PollMessage.Autorenew; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; +import google.registry.model.reporting.DomainTransactionRecord; +import google.registry.model.reporting.DomainTransactionRecord.TransactionFieldAmount; import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.IcannReportingTypes.ActivityReportField; import google.registry.tmch.LordnTask; import javax.inject.Inject; import org.joda.time.DateTime; +import org.joda.time.Duration; /** * An EPP flow that creates a new domain resource. @@ -258,7 +262,8 @@ public class DomainCreateFlow implements TransactionalFlow { validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class)); String repoId = createDomainRepoId(ObjectifyService.allocateId(), registry.getTldStr()); DateTime registrationExpirationTime = leapSafeAddYears(now, years); - HistoryEntry historyEntry = buildHistoryEntry(repoId, period, now); + HistoryEntry historyEntry = buildHistoryEntry( + repoId, period, now, registry.getAddGracePeriodLength(), registry.getTldStr()); // Bill for the create. BillingEvent.OneTime createBillingEvent = createOneTimeBillingEvent( @@ -360,12 +365,18 @@ public class DomainCreateFlow implements TransactionalFlow { } } - private HistoryEntry buildHistoryEntry(String repoId, Period period, DateTime now) { + private HistoryEntry buildHistoryEntry( + String repoId, Period period, DateTime now, Duration addGracePeriod, String tld) { return historyBuilder .setType(HistoryEntry.Type.DOMAIN_CREATE) .setPeriod(period) .setModificationTime(now) .setParent(Key.create(DomainResource.class, repoId)) + .setDomainTransactionRecord( + DomainTransactionRecord.create( + tld, + now.plus(addGracePeriod), + TransactionFieldAmount.create(netAddsFieldFromYears(period.getValue()), 1))) .build(); } diff --git a/java/google/registry/flows/domain/DomainRenewFlow.java b/java/google/registry/flows/domain/DomainRenewFlow.java index 2f753c87a..ac535640c 100644 --- a/java/google/registry/flows/domain/DomainRenewFlow.java +++ b/java/google/registry/flows/domain/DomainRenewFlow.java @@ -28,6 +28,7 @@ import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge; import static google.registry.flows.domain.DomainFlowUtils.validateRegistrationPeriod; import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.model.reporting.DomainTransactionRecord.TransactionFieldAmount.TransactionReportField.netRenewsFieldFromYears; import static google.registry.util.DateTimeUtils.leapSafeAddYears; import com.google.common.base.Optional; @@ -56,6 +57,7 @@ import google.registry.model.domain.DomainCommand.Renew; import google.registry.model.domain.DomainRenewData; import google.registry.model.domain.DomainResource; import google.registry.model.domain.GracePeriod; +import google.registry.model.domain.Period; import google.registry.model.domain.fee.BaseFee.FeeType; import google.registry.model.domain.fee.Fee; import google.registry.model.domain.fee.FeeRenewCommandExtension; @@ -69,11 +71,14 @@ import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.EppResponse; import google.registry.model.poll.PollMessage; import google.registry.model.registry.Registry; +import google.registry.model.reporting.DomainTransactionRecord; +import google.registry.model.reporting.DomainTransactionRecord.TransactionFieldAmount; import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.IcannReportingTypes.ActivityReportField; import javax.inject.Inject; import org.joda.money.Money; import org.joda.time.DateTime; +import org.joda.time.Duration; /** * An EPP flow that renews a domain. @@ -150,12 +155,9 @@ public final class DomainRenewFlow implements TransactionalFlow { .setNow(now) .setYears(years) .build()); - HistoryEntry historyEntry = historyBuilder - .setType(HistoryEntry.Type.DOMAIN_RENEW) - .setPeriod(command.getPeriod()) - .setModificationTime(now) - .setParent(Key.create(existingDomain)) - .build(); + Registry registry = Registry.get(existingDomain.getTld()); + HistoryEntry historyEntry = buildHistoryEntry( + existingDomain, command.getPeriod(), now, registry.getRenewGracePeriodLength()); String tld = existingDomain.getTld(); // Bill for this explicit renew itself. BillingEvent.OneTime explicitRenewEvent = @@ -211,6 +213,21 @@ public final class DomainRenewFlow implements TransactionalFlow { .build(); } + private HistoryEntry buildHistoryEntry( + DomainResource existingDomain, Period period, DateTime now, Duration renewGracePeriod) { + return historyBuilder + .setType(HistoryEntry.Type.DOMAIN_RENEW) + .setPeriod(period) + .setModificationTime(now) + .setParent(Key.create(existingDomain)) + .setDomainTransactionRecord( + DomainTransactionRecord.create( + existingDomain.getTld(), + now.plus(renewGracePeriod), + TransactionFieldAmount.create(netRenewsFieldFromYears(period.getValue()), 1))) + .build(); + } + private void verifyRenewAllowed( Optional authInfo, DomainResource existingDomain, diff --git a/java/google/registry/flows/domain/DomainRestoreRequestFlow.java b/java/google/registry/flows/domain/DomainRestoreRequestFlow.java index 67f4473ed..ab0bc0ad1 100644 --- a/java/google/registry/flows/domain/DomainRestoreRequestFlow.java +++ b/java/google/registry/flows/domain/DomainRestoreRequestFlow.java @@ -173,18 +173,13 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow { } private HistoryEntry buildHistoryEntry(DomainResource existingDomain, DateTime now) { - DomainTransactionRecord transactionRecord = - new DomainTransactionRecord.Builder() - .setTld(existingDomain.getTld()) - .setReportingTime(now) - .setTransactionFieldAmounts( - ImmutableSet.of(TransactionFieldAmount.create(RESTORED_DOMAINS, 1))) - .build(); return historyBuilder .setType(HistoryEntry.Type.DOMAIN_RESTORE) .setModificationTime(now) .setParent(Key.create(existingDomain)) - .setDomainTransactionRecord(transactionRecord) + .setDomainTransactionRecord( + DomainTransactionRecord.create( + existingDomain.getTld(), now, TransactionFieldAmount.create(RESTORED_DOMAINS, 1))) .build(); } diff --git a/java/google/registry/model/reporting/DomainTransactionRecord.java b/java/google/registry/model/reporting/DomainTransactionRecord.java index d3775d7cc..8bedc167b 100644 --- a/java/google/registry/model/reporting/DomainTransactionRecord.java +++ b/java/google/registry/model/reporting/DomainTransactionRecord.java @@ -14,6 +14,7 @@ package google.registry.model.reporting; +import static com.google.common.base.Preconditions.checkArgument; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.common.collect.ImmutableSet; @@ -118,7 +119,28 @@ public class DomainTransactionRecord extends ImmutableObject implements Buildabl TRANSFER_LOSING_NACKED, DELETED_DOMAINS_GRACE, DELETED_DOMAINS_NOGRACE, - RESTORED_DOMAINS + RESTORED_DOMAINS; + + /** Boilerplate to simplify getting the NET_ADDS_#_YR enum from a number of years. */ + public static TransactionReportField netAddsFieldFromYears(int years) { + return nameToField("NET_ADDS_%d_YR", years); + } + + /** Boilerplate to simplify getting the NET_RENEWS_#_YR enum from a number of years. */ + public static TransactionReportField netRenewsFieldFromYears(int years) { + return nameToField("NET_RENEWS_%d_YR", years); + } + + private static TransactionReportField nameToField(String enumTemplate, int years) { + checkArgument( + years >= 1 && years <= 10, "domain add and renew years must be between 1 and 10"); + try { + return TransactionReportField.valueOf(String.format(enumTemplate, years)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException( + "Unexpected error converting add/renew years to enum TransactionReportField", e); + } + } } } @@ -134,6 +156,19 @@ public class DomainTransactionRecord extends ImmutableObject implements Buildabl return transactionFieldAmounts; } + /** An alternative construction method when the builder is not necessary. */ + public static DomainTransactionRecord create( + String tld, + DateTime reportingTime, + TransactionFieldAmount... transactionFieldAmounts) { + return new DomainTransactionRecord.Builder() + .setTld(tld) + // We report this event when the grace period ends, if applicable + .setReportingTime(reportingTime) + .setTransactionFieldAmounts(ImmutableSet.copyOf(transactionFieldAmounts)) + .build(); + } + @Override public Builder asBuilder() { return new Builder(clone(this)); diff --git a/javatests/google/registry/flows/domain/DomainAllocateFlowTest.java b/javatests/google/registry/flows/domain/DomainAllocateFlowTest.java index ddb8fe1e9..83c14587a 100644 --- a/javatests/google/registry/flows/domain/DomainAllocateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainAllocateFlowTest.java @@ -18,6 +18,7 @@ import static com.google.common.io.BaseEncoding.base16; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.EppResourceUtils.loadDomainApplication; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.model.reporting.DomainTransactionRecord.TransactionFieldAmount.TransactionReportField.netAddsFieldFromYears; import static google.registry.testing.DatastoreHelper.assertBillingEvents; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType; @@ -71,6 +72,8 @@ import google.registry.model.poll.PendingActionNotificationResponse.DomainPendin import google.registry.model.poll.PollMessage; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; +import google.registry.model.reporting.DomainTransactionRecord; +import google.registry.model.reporting.DomainTransactionRecord.TransactionFieldAmount; import google.registry.model.reporting.HistoryEntry; import google.registry.model.smd.EncodedSignedMark; import google.registry.testing.TaskQueueHelper.TaskMatcher; @@ -106,10 +109,14 @@ public class DomainAllocateFlowTest private void setupDomainApplication(String tld, TldState tldState) throws Exception { createTld(tld, tldState); - persistResource(Registry.get(tld).asBuilder().setReservedLists(persistReservedList( - tld + "-reserved", - "reserved-label,FULLY_BLOCKED", - "collision-label,NAME_COLLISION")).build()); + persistResource(Registry.get(tld).asBuilder() + .setReservedLists( + persistReservedList( + tld + "-reserved", + "reserved-label,FULLY_BLOCKED", + "collision-label,NAME_COLLISION")) + .setAddGracePeriodLength(Duration.standardMinutes(9)) + .build()); String domainName = getUniqueIdFromCommand(); application = persistResource(newDomainApplication(domainName).asBuilder() .setCreationTrid(TRID) @@ -586,7 +593,7 @@ public class DomainAllocateFlowTest } @Test - public void testSucess_quietPeriod() throws Exception { + public void testSuccess_quietPeriod() throws Exception { setupDomainApplication("tld", TldState.QUIET_PERIOD); createTld("tld", TldState.QUIET_PERIOD); doSuccessfulTest(2); @@ -655,4 +662,25 @@ public class DomainAllocateFlowTest // Ensure we log the client ID for srs-dom-create so we can also use it for attempted-adds. assertClientIdFieldLogged("TheRegistrar"); } + + @Test + public void testIcannTransactionRecord_getsStored() throws Exception { + setupDomainApplication("tld", TldState.QUIET_PERIOD); + persistResource( + Registry.get("tld") + .asBuilder() + .setRenewGracePeriodLength(Duration.standardMinutes(9)) + .build()); + runFlow(CommitMode.LIVE, UserPrivileges.SUPERUSER); + DomainResource domain = reloadResourceByForeignKey(); + HistoryEntry historyEntry = + getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_ALLOCATE); + DomainTransactionRecord transactionRecord = historyEntry.getDomainTransactionRecord(); + + assertThat(transactionRecord.getTld()).isEqualTo("tld"); + assertThat(transactionRecord.getReportingTime()) + .isEqualTo(historyEntry.getModificationTime().plusMinutes(9)); + assertThat(transactionRecord.getTransactionFieldAmounts()) + .containsExactly(TransactionFieldAmount.create(netAddsFieldFromYears(2), 1)); + } } diff --git a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java index 73ad6d1dd..3ab08c3ba 100644 --- a/javatests/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCreateFlowTest.java @@ -22,6 +22,7 @@ import static google.registry.model.eppcommon.StatusValue.OK; import static google.registry.model.eppcommon.StatusValue.SERVER_TRANSFER_PROHIBITED; import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.model.reporting.DomainTransactionRecord.TransactionFieldAmount.TransactionReportField.netAddsFieldFromYears; import static google.registry.pricing.PricingEngineProxy.isDomainPremium; import static google.registry.testing.DatastoreHelper.assertBillingEvents; import static google.registry.testing.DatastoreHelper.assertPollMessagesForResource; @@ -127,6 +128,8 @@ import google.registry.model.eppcommon.StatusValue; import google.registry.model.poll.PollMessage; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; +import google.registry.model.reporting.DomainTransactionRecord; +import google.registry.model.reporting.DomainTransactionRecord.TransactionFieldAmount; import google.registry.model.reporting.HistoryEntry; import google.registry.monitoring.whitebox.EppMetric; import google.registry.testing.DatastoreHelper; @@ -1989,6 +1992,27 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase