diff --git a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java
index f390a56d2..080e5c90f 100644
--- a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java
+++ b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java
@@ -14,8 +14,10 @@
package google.registry.model.reporting;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.googlecode.objectify.Key.getKind;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
+import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
@@ -383,6 +385,13 @@ public class HistoryEntry extends ImmutableObject implements Buildable, Datastor
@Override
public T build() {
+ // TODO(mcilwain): Add null checking for id/parent once DB migration is complete.
+ checkArgumentNotNull(getInstance().type, "History entry type must be specified");
+ checkArgumentNotNull(getInstance().modificationTime, "Modification time must be specified");
+ checkArgumentNotNull(getInstance().clientId, "Registrar ID must be specified");
+ checkArgument(
+ !getInstance().type.equals(Type.SYNTHETIC) || !getInstance().requestedByRegistrar,
+ "Synthetic history entries cannot be requested by a registrar");
return super.build();
}
diff --git a/core/src/main/java/google/registry/tools/DedupeRecurringBillingEventIdsCommand.java b/core/src/main/java/google/registry/tools/DedupeRecurringBillingEventIdsCommand.java
deleted file mode 100644
index e131e4346..000000000
--- a/core/src/main/java/google/registry/tools/DedupeRecurringBillingEventIdsCommand.java
+++ /dev/null
@@ -1,191 +0,0 @@
-// Copyright 2020 The Nomulus Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package google.registry.tools;
-
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
-import static google.registry.model.ofy.ObjectifyService.auditedOfy;
-
-import com.beust.jcommander.Parameters;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import com.googlecode.objectify.Key;
-import google.registry.model.billing.BillingEvent;
-import google.registry.model.billing.BillingEvent.OneTime;
-import google.registry.model.billing.BillingEvent.Recurring;
-import google.registry.model.domain.DomainBase;
-import google.registry.model.domain.GracePeriod;
-import google.registry.model.transfer.DomainTransferData;
-import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
-import google.registry.persistence.VKey;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A command that re-saves the problematic {@link BillingEvent.Recurring} entities with unique IDs.
- *
- *
This command is used to address the duplicate id issue we found for certain {@link
- * BillingEvent.Recurring} entities. The command reassigns an application wide unique id to the
- * problematic entity and resaves it, it also resaves the entity having reference to the problematic
- * entity with the updated id.
- *
- *
To use this command, you will need to provide the path to a file containing a list of strings
- * representing the literal of Objectify key for the problematic entities. An example key literal
- * is:
- *
- *
- * "DomainBase", "111111-TEST", "HistoryEntry", 2222222, "Recurring", 3333333
- *
- *
- * Note that the double quotes are part of the key literal. The key literal can be retrieved from
- * the column __key__.path
in BigQuery.
- */
-@Parameters(
- separators = " =",
- commandDescription = "Dedupe BillingEvent.Recurring entities with duplicate IDs.")
-public class DedupeRecurringBillingEventIdsCommand extends ReadEntityFromKeyPathCommand {
-
- @Override
- void process(Recurring recurring) {
- // Loads the associated DomainBase and BillingEvent.OneTime entities that
- // may have reference to this BillingEvent.Recurring entity.
- Key domainKey = getGrandParentAsDomain(Key.create(recurring));
- DomainBase domain = auditedOfy().load().key(domainKey).now();
- List oneTimes =
- auditedOfy().load().type(BillingEvent.OneTime.class).ancestor(domainKey).list();
-
- VKey oldRecurringVKey = recurring.createVKey();
- // By setting id to 0L, Buildable.build() will assign an application wide unique id to it.
- Recurring uniqIdRecurring = recurring.asBuilder().setId(0L).build();
- VKey newRecurringVKey = uniqIdRecurring.createVKey();
-
- // After having the unique id for the BillingEvent.Recurring entity, we also need to
- // update the references in other entities to point to the new BillingEvent.Recurring
- // entity.
- updateReferenceInOneTimeBillingEvent(oneTimes, oldRecurringVKey, newRecurringVKey);
- updateReferenceInDomain(domain, oldRecurringVKey, newRecurringVKey);
-
- stageEntityKeyChange(recurring, uniqIdRecurring);
- }
-
- /**
- * Resaves {@link BillingEvent.OneTime} entities with updated {@link
- * BillingEvent.OneTime#cancellationMatchingBillingEvent}.
- *
- * {@link BillingEvent.OneTime#cancellationMatchingBillingEvent} is a {@link VKey} to a {@link
- * BillingEvent.Recurring} entity. So, if the {@link BillingEvent.Recurring} entity gets a new key
- * by changing its id, we need to update {@link
- * BillingEvent.OneTime#cancellationMatchingBillingEvent} as well.
- */
- private void updateReferenceInOneTimeBillingEvent(
- List oneTimes, VKey oldRecurringVKey, VKey newRecurringVKey) {
- oneTimes.forEach(
- oneTime -> {
- if (oneTime.getCancellationMatchingBillingEvent() != null
- && oneTime.getCancellationMatchingBillingEvent().equals(oldRecurringVKey)) {
- BillingEvent.OneTime updatedOneTime =
- oneTime.asBuilder().setCancellationMatchingBillingEvent(newRecurringVKey).build();
- stageEntityChange(oneTime, updatedOneTime);
- appendChangeMessage(
- String.format(
- "Changed cancellationMatchingBillingEvent in entity %s from %s to %s\n",
- oneTime.createVKey().getOfyKey(),
- oneTime.getCancellationMatchingBillingEvent().getOfyKey(),
- updatedOneTime.getCancellationMatchingBillingEvent().getOfyKey()));
- }
- });
- }
-
- /**
- * Resaves {@link DomainBase} entity with updated references to {@link BillingEvent.Recurring}
- * entity.
- *
- * The following 4 fields in the domain entity can be or have a reference to this
- * BillingEvent.Recurring entity, so we need to check them and replace them with the new entity
- * when necessary:
- *
- *
- * - domain.autorenewBillingEvent, see {@link DomainBase#autorenewBillingEvent}
- *
- domain.transferData.serverApproveAutorenewEvent, see {@link
- * DomainTransferData#serverApproveAutorenewEvent}
- *
- domain.transferData.serverApproveEntities, see {@link
- * DomainTransferData#serverApproveEntities}
- *
- domain.gracePeriods.billingEventRecurring, see {@link GracePeriod#billingEventRecurring}
- *
- */
- private void updateReferenceInDomain(
- DomainBase domain, VKey oldRecurringVKey, VKey newRecurringVKey) {
- DomainBase.Builder domainBuilder = domain.asBuilder();
- StringBuilder domainChange =
- new StringBuilder(
- String.format(
- "Resaved domain %s with following changes:\n", domain.createVKey().getOfyKey()));
-
- if (domain.getAutorenewBillingEvent() != null
- && domain.getAutorenewBillingEvent().equals(oldRecurringVKey)) {
- domainBuilder.setAutorenewBillingEvent(newRecurringVKey);
- domainChange.append(
- String.format(
- " Changed autorenewBillingEvent from %s to %s.\n",
- oldRecurringVKey, newRecurringVKey));
- }
-
- if (domain.getTransferData().getServerApproveAutorenewEvent() != null
- && domain.getTransferData().getServerApproveAutorenewEvent().equals(oldRecurringVKey)) {
- Set> serverApproveEntities =
- Sets.union(
- Sets.difference(
- domain.getTransferData().getServerApproveEntities(),
- ImmutableSet.of(oldRecurringVKey)),
- ImmutableSet.of(newRecurringVKey));
- domainBuilder.setTransferData(
- domain
- .getTransferData()
- .asBuilder()
- .setServerApproveEntities(ImmutableSet.copyOf(serverApproveEntities))
- .setServerApproveAutorenewEvent(newRecurringVKey)
- .build());
- domainChange.append(
- String.format(
- " Changed transferData.serverApproveAutoRenewEvent from %s to %s.\n",
- oldRecurringVKey, newRecurringVKey));
- domainChange.append(
- String.format(
- " Changed transferData.serverApproveEntities to remove %s and add %s.\n",
- oldRecurringVKey, newRecurringVKey));
- }
-
- ImmutableSet updatedGracePeriod =
- domain.getGracePeriods().stream()
- .map(
- gracePeriod ->
- gracePeriod.getRecurringBillingEvent().equals(oldRecurringVKey)
- ? gracePeriod.cloneWithRecurringBillingEvent(newRecurringVKey)
- : gracePeriod)
- .collect(toImmutableSet());
- if (!updatedGracePeriod.equals(domain.getGracePeriods())) {
- domainBuilder.setGracePeriods(updatedGracePeriod);
- domainChange.append(
- String.format(
- " Changed gracePeriods to remove %s and add %s.\n",
- oldRecurringVKey, newRecurringVKey));
- }
-
- DomainBase updatedDomain = domainBuilder.build();
- if (!updatedDomain.equals(domain)) {
- stageEntityChange(domain, updatedDomain);
- appendChangeMessage(domainChange.toString());
- }
- }
-}
diff --git a/core/src/main/java/google/registry/tools/RegistryTool.java b/core/src/main/java/google/registry/tools/RegistryTool.java
index ebfea9bde..0f808b58a 100644
--- a/core/src/main/java/google/registry/tools/RegistryTool.java
+++ b/core/src/main/java/google/registry/tools/RegistryTool.java
@@ -53,7 +53,6 @@ public final class RegistryTool {
.put("create_tld", CreateTldCommand.class)
.put("curl", CurlCommand.class)
.put("dedupe_one_time_billing_event_ids", DedupeOneTimeBillingEventIdsCommand.class)
- .put("dedupe_recurring_billing_event_ids", DedupeRecurringBillingEventIdsCommand.class)
.put("delete_allocation_tokens", DeleteAllocationTokensCommand.class)
.put("delete_contact_by_roid", DeleteContactByRoidCommand.class)
.put("delete_domain", DeleteDomainCommand.class)
diff --git a/core/src/test/java/google/registry/batch/DeleteExpiredDomainsActionTest.java b/core/src/test/java/google/registry/batch/DeleteExpiredDomainsActionTest.java
index 06c823d05..e5e135561 100644
--- a/core/src/test/java/google/registry/batch/DeleteExpiredDomainsActionTest.java
+++ b/core/src/test/java/google/registry/batch/DeleteExpiredDomainsActionTest.java
@@ -170,6 +170,7 @@ class DeleteExpiredDomainsActionTest {
.setType(DOMAIN_CREATE)
.setParent(pendingExpirationDomain)
.setModificationTime(clock.nowUtc().minusMonths(9))
+ .setClientId(pendingExpirationDomain.getCreationClientId())
.build());
BillingEvent.Recurring autorenewBillingEvent =
persistResource(createAutorenewBillingEvent(createHistoryEntry).build());
diff --git a/core/src/test/java/google/registry/batch/DeleteProberDataActionTest.java b/core/src/test/java/google/registry/batch/DeleteProberDataActionTest.java
index 124c06edf..8c8207a88 100644
--- a/core/src/test/java/google/registry/batch/DeleteProberDataActionTest.java
+++ b/core/src/test/java/google/registry/batch/DeleteProberDataActionTest.java
@@ -279,11 +279,14 @@ class DeleteProberDataActionTest extends MapreduceTestCase persistDomainAndDescendants(String fqdn) {
DomainBase domain = persistDeletedDomain(fqdn, DELETION_TIME);
- HistoryEntry historyEntry = persistSimpleResource(
- new HistoryEntry.Builder()
- .setParent(domain)
- .setType(HistoryEntry.Type.DOMAIN_CREATE)
- .build());
+ HistoryEntry historyEntry =
+ persistSimpleResource(
+ new HistoryEntry.Builder()
+ .setParent(domain)
+ .setType(HistoryEntry.Type.DOMAIN_CREATE)
+ .setClientId("TheRegistrar")
+ .setModificationTime(DELETION_TIME.minusYears(3))
+ .build());
BillingEvent.OneTime billingEvent = persistSimpleResource(
new BillingEvent.OneTime.Builder()
.setParent(historyEntry)
diff --git a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java
index 6b454eb56..8ca36fdd6 100644
--- a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java
+++ b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java
@@ -19,6 +19,7 @@ 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.auditedOfy;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_AUTORENEW;
+import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.assertBillingEvents;
import static google.registry.testing.DatabaseHelper.assertBillingEventsForResource;
@@ -84,9 +85,20 @@ public class ExpandRecurringBillingEventsActionTest
action.clock = clock;
action.cursorTimeParam = Optional.empty();
createTld("tld");
- domain = persistResource(newDomainBase("example.tld").asBuilder()
- .setCreationTimeForTest(DateTime.parse("1999-01-05T00:00:00Z")).build());
- historyEntry = persistResource(new HistoryEntry.Builder().setParent(domain).build());
+ domain =
+ persistResource(
+ newDomainBase("example.tld")
+ .asBuilder()
+ .setCreationTimeForTest(DateTime.parse("1999-01-05T00:00:00Z"))
+ .build());
+ historyEntry =
+ persistResource(
+ new HistoryEntry.Builder()
+ .setClientId(domain.getCreationClientId())
+ .setType(HistoryEntry.Type.DOMAIN_CREATE)
+ .setModificationTime(DateTime.parse("1999-01-05T00:00:00Z"))
+ .setParent(domain)
+ .build());
recurring =
new BillingEvent.Recurring.Builder()
.setParent(historyEntry)
@@ -174,7 +186,14 @@ public class ExpandRecurringBillingEventsActionTest
void testSuccess_expandSingleEvent_deletedDomain() throws Exception {
DateTime deletionTime = DateTime.parse("2000-08-01T00:00:00Z");
DomainBase deletedDomain = persistDeletedDomain("deleted.tld", deletionTime);
- historyEntry = persistResource(new HistoryEntry.Builder().setParent(deletedDomain).build());
+ historyEntry =
+ persistResource(
+ new HistoryEntry.Builder()
+ .setParent(deletedDomain)
+ .setClientId(deletedDomain.getCreationClientId())
+ .setModificationTime(deletedDomain.getCreationTime())
+ .setType(DOMAIN_CREATE)
+ .build());
recurring =
persistResource(
new BillingEvent.Recurring.Builder()
diff --git a/core/src/test/java/google/registry/beam/initsql/DomainBaseUtilTest.java b/core/src/test/java/google/registry/beam/initsql/DomainBaseUtilTest.java
index 8707bd48d..b45bd27b3 100644
--- a/core/src/test/java/google/registry/beam/initsql/DomainBaseUtilTest.java
+++ b/core/src/test/java/google/registry/beam/initsql/DomainBaseUtilTest.java
@@ -100,7 +100,14 @@ public class DomainBaseUtilTest {
.build())
.createVKey();
Key historyEntryKey =
- Key.create(persistResource(new HistoryEntry.Builder().setParent(domainKey).build()));
+ Key.create(
+ persistResource(
+ new HistoryEntry.Builder()
+ .setParent(domainKey)
+ .setType(HistoryEntry.Type.DOMAIN_CREATE)
+ .setClientId("TheRegistrar")
+ .setModificationTime(fakeClock.nowUtc().minusYears(1))
+ .build()));
oneTimeBillKey = Key.create(historyEntryKey, BillingEvent.OneTime.class, 1);
recurringBillKey = VKey.from(Key.create(historyEntryKey, BillingEvent.Recurring.class, 2));
VKey autorenewPollKey =
diff --git a/core/src/test/java/google/registry/beam/initsql/InitSqlPipelineTest.java b/core/src/test/java/google/registry/beam/initsql/InitSqlPipelineTest.java
index 9800c93d1..294638b20 100644
--- a/core/src/test/java/google/registry/beam/initsql/InitSqlPipelineTest.java
+++ b/core/src/test/java/google/registry/beam/initsql/InitSqlPipelineTest.java
@@ -181,6 +181,7 @@ class InitSqlPipelineTest {
new HistoryEntry.Builder()
.setParent(domainKey)
.setModificationTime(fakeClock.nowUtc())
+ .setClientId(registrar1.getClientId())
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.build());
persistResource(
diff --git a/core/src/test/java/google/registry/flows/domain/DomainDeleteFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainDeleteFlowTest.java
index e99ea967a..a86ccda8d 100644
--- a/core/src/test/java/google/registry/flows/domain/DomainDeleteFlowTest.java
+++ b/core/src/test/java/google/registry/flows/domain/DomainDeleteFlowTest.java
@@ -179,6 +179,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase {
void testSuccess_contactDelete() throws Exception {
// Contact delete poll messages do not have any response data, so ensure that no
// response data block is produced in the poll message.
- HistoryEntry historyEntry = persistResource(new HistoryEntry.Builder()
- .setClientId("NewRegistrar")
- .setModificationTime(clock.nowUtc().minusDays(1))
- .setType(HistoryEntry.Type.CONTACT_DELETE)
- .setParent(contact)
- .build());
+ HistoryEntry historyEntry =
+ persistResource(
+ new HistoryEntry.Builder()
+ .setClientId("NewRegistrar")
+ .setModificationTime(clock.nowUtc().minusDays(1))
+ .setType(HistoryEntry.Type.CONTACT_DELETE)
+ .setParent(contact)
+ .build());
persistResource(
new PollMessage.OneTime.Builder()
.setClientId("NewRegistrar")
@@ -229,12 +231,14 @@ class PollRequestFlowTest extends FlowTestCase {
void testSuccess_hostDelete() throws Exception {
// Host delete poll messages do not have any response data, so ensure that no
// response data block is produced in the poll message.
- HistoryEntry historyEntry = persistResource(new HistoryEntry.Builder()
- .setClientId("NewRegistrar")
- .setModificationTime(clock.nowUtc().minusDays(1))
- .setType(HistoryEntry.Type.HOST_DELETE)
- .setParent(host)
- .build());
+ HistoryEntry historyEntry =
+ persistResource(
+ new HistoryEntry.Builder()
+ .setClientId("NewRegistrar")
+ .setModificationTime(clock.nowUtc().minusDays(1))
+ .setType(HistoryEntry.Type.HOST_DELETE)
+ .setParent(host)
+ .build());
persistResource(
new PollMessage.OneTime.Builder()
.setClientId("NewRegistrar")
diff --git a/core/src/test/java/google/registry/mapreduce/inputs/ChildEntityInputTest.java b/core/src/test/java/google/registry/mapreduce/inputs/ChildEntityInputTest.java
index 002ee33a8..429e6e380 100644
--- a/core/src/test/java/google/registry/mapreduce/inputs/ChildEntityInputTest.java
+++ b/core/src/test/java/google/registry/mapreduce/inputs/ChildEntityInputTest.java
@@ -80,10 +80,22 @@ class ChildEntityInputTest {
domainA = persistEppResourceInFirstBucket(newDomainBase("a.tld", contact));
domainHistoryEntryA =
persistResource(
- new DomainHistory.Builder().setDomain(domainA).setModificationTime(now).build());
+ new DomainHistory.Builder()
+ .setDomain(domainA)
+ .setType(HistoryEntry.Type.DOMAIN_CREATE)
+ .setDomain(domainA)
+ .setModificationTime(now)
+ .setClientId(domainA.getCreationClientId())
+ .build());
contactHistoryEntry =
persistResource(
- new ContactHistory.Builder().setContact(contact).setModificationTime(now).build());
+ new ContactHistory.Builder()
+ .setContact(contact)
+ .setType(HistoryEntry.Type.CONTACT_CREATE)
+ .setContact(contact)
+ .setModificationTime(now)
+ .setClientId(contact.getCreationClientId())
+ .build());
oneTimeA =
persistResource(
new BillingEvent.OneTime.Builder()
@@ -113,7 +125,13 @@ class ChildEntityInputTest {
domainB = persistEppResourceInFirstBucket(newDomainBase("b.tld"));
domainHistoryEntryB =
persistResource(
- new DomainHistory.Builder().setDomain(domainB).setModificationTime(now).build());
+ new DomainHistory.Builder()
+ .setDomain(domainB)
+ .setType(HistoryEntry.Type.DOMAIN_CREATE)
+ .setDomain(domainB)
+ .setModificationTime(now)
+ .setClientId(domainB.getCreationClientId())
+ .build());
oneTimeB =
persistResource(
new BillingEvent.OneTime.Builder()
@@ -295,8 +313,9 @@ class ChildEntityInputTest {
persistResource(
new DomainHistory.Builder()
.setDomain(domain)
+ .setType(HistoryEntry.Type.DOMAIN_CREATE)
.setModificationTime(now)
- .setClientId(i + ".tld")
+ .setClientId(domain.getCreationClientId())
.build())
.asHistoryEntry());
persistResource(EppResourceIndex.create(getBucketKey(i), Key.create(domain)));
diff --git a/core/src/test/java/google/registry/model/billing/BillingEventTest.java b/core/src/test/java/google/registry/model/billing/BillingEventTest.java
index ddd1e3494..184bfa903 100644
--- a/core/src/test/java/google/registry/model/billing/BillingEventTest.java
+++ b/core/src/test/java/google/registry/model/billing/BillingEventTest.java
@@ -80,6 +80,7 @@ public class BillingEventTest extends EntityTestCase {
.setDomain(domain)
.setModificationTime(now)
.setRequestedByRegistrar(false)
+ .setClientId("TheRegistrar")
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setXmlBytes(new byte[0])
.build());
@@ -89,6 +90,7 @@ public class BillingEventTest extends EntityTestCase {
.setDomain(domain)
.setModificationTime(now.plusDays(1))
.setRequestedByRegistrar(false)
+ .setClientId("TheRegistrar")
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setXmlBytes(new byte[0])
.build());
diff --git a/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java b/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java
index 0f3942db8..3c3dc160c 100644
--- a/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java
+++ b/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java
@@ -443,7 +443,7 @@ public class DomainBaseSqlTest {
.setModificationTime(DateTime.now(UTC))
.setParent(Key.create(DomainBase.class, "4-COM"))
.setDomainRepoId("4-COM")
-
+ .setClientId("registrar1")
// These are non-null, but I don't think some tests set them.
.setReason("felt like it")
.setRequestedByRegistrar(false)
@@ -574,7 +574,7 @@ public class DomainBaseSqlTest {
.setModificationTime(DateTime.now(UTC))
.setParent(Key.create(DomainBase.class, "4-COM"))
.setDomainRepoId("4-COM")
-
+ .setClientId("registrar1")
// These are non-null, but I don't think some tests set them.
.setReason("felt like it")
.setRequestedByRegistrar(false)
diff --git a/core/src/test/java/google/registry/model/domain/DomainBaseTest.java b/core/src/test/java/google/registry/model/domain/DomainBaseTest.java
index 0f4dd593e..0c8d934c2 100644
--- a/core/src/test/java/google/registry/model/domain/DomainBaseTest.java
+++ b/core/src/test/java/google/registry/model/domain/DomainBaseTest.java
@@ -63,6 +63,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link DomainBase}. */
+@SuppressWarnings("WeakerAccess") // Referred to by EppInputTest.
public class DomainBaseTest extends EntityTestCase {
private DomainBase domain;
@@ -98,7 +99,13 @@ public class DomainBaseTest extends EntityTestCase {
.createVKey();
historyEntryKey =
Key.create(
- persistResource(new HistoryEntry.Builder().setParent(domainKey.getOfyKey()).build()));
+ persistResource(
+ new HistoryEntry.Builder()
+ .setParent(domainKey.getOfyKey())
+ .setModificationTime(fakeClock.nowUtc())
+ .setType(HistoryEntry.Type.DOMAIN_CREATE)
+ .setClientId("aregistrar")
+ .build()));
oneTimeBillKey = VKey.from(Key.create(historyEntryKey, BillingEvent.OneTime.class, 1));
recurringBillKey = VKey.from(Key.create(historyEntryKey, BillingEvent.Recurring.class, 2));
VKey autorenewPollKey =
@@ -112,7 +119,7 @@ public class DomainBaseTest extends EntityTestCase {
new DomainBase.Builder()
.setDomainName("example.com")
.setRepoId("4-COM")
- .setCreationClientId("a registrar")
+ .setCreationClientId("aregistrar")
.setLastEppUpdateTime(fakeClock.nowUtc())
.setLastEppUpdateClientId("AnotherRegistrar")
.setLastTransferTime(fakeClock.nowUtc())
@@ -356,7 +363,13 @@ public class DomainBaseTest extends EntityTestCase {
}
private void doExpiredTransferTest(DateTime oldExpirationTime) {
- HistoryEntry historyEntry = new HistoryEntry.Builder().setParent(domain).build();
+ HistoryEntry historyEntry =
+ new HistoryEntry.Builder()
+ .setParent(domain)
+ .setModificationTime(fakeClock.nowUtc())
+ .setClientId(domain.getCurrentSponsorClientId())
+ .setType(HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST)
+ .build();
BillingEvent.OneTime transferBillingEvent =
persistResource(
new BillingEvent.OneTime.Builder()
diff --git a/core/src/test/java/google/registry/model/ofy/OfyTest.java b/core/src/test/java/google/registry/model/ofy/OfyTest.java
index 935d9ec21..d5d8e29a8 100644
--- a/core/src/test/java/google/registry/model/ofy/OfyTest.java
+++ b/core/src/test/java/google/registry/model/ofy/OfyTest.java
@@ -71,8 +71,9 @@ public class OfyTest {
createTld("tld");
someObject =
new HistoryEntry.Builder()
- .setClientId("client id")
+ .setClientId("clientid")
.setModificationTime(START_OF_TIME)
+ .setType(HistoryEntry.Type.CONTACT_CREATE)
.setParent(persistActiveContact("parentContact"))
.setTrid(Trid.create("client", "server"))
.setXmlBytes("".getBytes(UTF_8))
diff --git a/core/src/test/java/google/registry/model/reporting/HistoryEntryTest.java b/core/src/test/java/google/registry/model/reporting/HistoryEntryTest.java
index 8f4f340dc..39d1df8a7 100644
--- a/core/src/test/java/google/registry/model/reporting/HistoryEntryTest.java
+++ b/core/src/test/java/google/registry/model/reporting/HistoryEntryTest.java
@@ -14,6 +14,7 @@
package google.registry.model.reporting;
+import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
@@ -21,6 +22,7 @@ import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -33,6 +35,7 @@ import google.registry.model.reporting.DomainTransactionRecord.TransactionReport
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
+import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
/** Unit tests for {@link HistoryEntry}. */
@@ -87,6 +90,70 @@ class HistoryEntryTest extends EntityTestCase {
});
}
+ @TestOfyAndSql
+ void testBuilder_typeMustBeSpecified() {
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ new HistoryEntry.Builder()
+ .setId(5L)
+ .setModificationTime(DateTime.parse("1985-07-12T22:30:00Z"))
+ .setClientId("TheRegistrar")
+ .setReason("Reason")
+ .build());
+ assertThat(thrown).hasMessageThat().isEqualTo("History entry type must be specified");
+ }
+
+ @TestOfyAndSql
+ void testBuilder_modificationTimeMustBeSpecified() {
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ new HistoryEntry.Builder()
+ .setId(5L)
+ .setType(HistoryEntry.Type.CONTACT_CREATE)
+ .setClientId("TheRegistrar")
+ .setReason("Reason")
+ .build());
+ assertThat(thrown).hasMessageThat().isEqualTo("Modification time must be specified");
+ }
+
+ @TestOfyAndSql
+ void testBuilder_registrarIdMustBeSpecified() {
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ new HistoryEntry.Builder()
+ .setId(5L)
+ .setType(HistoryEntry.Type.CONTACT_CREATE)
+ .setModificationTime(DateTime.parse("1985-07-12T22:30:00Z"))
+ .setReason("Reason")
+ .build());
+ assertThat(thrown).hasMessageThat().isEqualTo("Registrar ID must be specified");
+ }
+
+ @TestOfyAndSql
+ void testBuilder_syntheticHistoryEntries_mustNotBeRequestedByRegistrar() {
+ IllegalArgumentException thrown =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ new HistoryEntry.Builder()
+ .setId(5L)
+ .setType(HistoryEntry.Type.SYNTHETIC)
+ .setModificationTime(DateTime.parse("1985-07-12T22:30:00Z"))
+ .setClientId("TheRegistrar")
+ .setReason("Reason")
+ .setRequestedByRegistrar(true)
+ .build());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo("Synthetic history entries cannot be requested by a registrar");
+ }
+
@TestOfyOnly
void testIndexing() throws Exception {
verifyIndexing(domainHistory.asHistoryEntry(), "modificationTime", "clientId");
diff --git a/core/src/test/java/google/registry/rde/DomainBaseToXjcConverterTest.java b/core/src/test/java/google/registry/rde/DomainBaseToXjcConverterTest.java
index 47ec369ef..dfeaec625 100644
--- a/core/src/test/java/google/registry/rde/DomainBaseToXjcConverterTest.java
+++ b/core/src/test/java/google/registry/rde/DomainBaseToXjcConverterTest.java
@@ -225,6 +225,7 @@ public class DomainBaseToXjcConverterTest {
.setModificationTime(clock.nowUtc())
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setDomain(domain)
+ .setClientId(domain.getCreationClientId())
.build());
BillingEvent.OneTime billingEvent =
persistResource(
diff --git a/core/src/test/java/google/registry/rde/RdeFixtures.java b/core/src/test/java/google/registry/rde/RdeFixtures.java
index f9a055d05..fed23535a 100644
--- a/core/src/test/java/google/registry/rde/RdeFixtures.java
+++ b/core/src/test/java/google/registry/rde/RdeFixtures.java
@@ -66,7 +66,13 @@ final class RdeFixtures {
.createVKey())
.build();
HistoryEntry historyEntry =
- persistResource(new HistoryEntry.Builder().setParent(domain).build());
+ persistResource(
+ new HistoryEntry.Builder()
+ .setParent(domain)
+ .setType(HistoryEntry.Type.DOMAIN_CREATE)
+ .setModificationTime(clock.nowUtc())
+ .setClientId("TheRegistrar")
+ .build());
clock.advanceOneMilli();
BillingEvent.OneTime billingEvent =
persistResourceWithCommitLog(
diff --git a/core/src/test/java/google/registry/testing/DatabaseHelper.java b/core/src/test/java/google/registry/testing/DatabaseHelper.java
index b03e9118e..dc90a8b78 100644
--- a/core/src/test/java/google/registry/testing/DatabaseHelper.java
+++ b/core/src/test/java/google/registry/testing/DatabaseHelper.java
@@ -545,6 +545,7 @@ public class DatabaseHelper {
.setType(HistoryEntry.Type.CONTACT_TRANSFER_REQUEST)
.setParent(persistResource(contact))
.setModificationTime(now)
+ .setClientId(contact.getCurrentSponsorClientId())
.build()
.toChildHistoryEntity());
return persistResource(
@@ -616,6 +617,7 @@ public class DatabaseHelper {
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setModificationTime(now)
.setDomain(domain)
+ .setClientId(domain.getCreationClientId())
.build());
BillingEvent.Recurring autorenewEvent =
persistResource(
@@ -657,6 +659,7 @@ public class DatabaseHelper {
.setType(HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST)
.setModificationTime(tm().transact(() -> tm().getTransactionTime()))
.setDomain(domain)
+ .setClientId("TheRegistrar")
.build());
BillingEvent.OneTime transferBillingEvent =
persistResource(
@@ -1085,7 +1088,7 @@ public class DatabaseHelper {
tm().put(
new HistoryEntry.Builder()
.setParent(resource)
- .setClientId(resource.getPersistedCurrentSponsorClientId())
+ .setClientId(resource.getCreationClientId())
.setType(getHistoryEntryType(resource))
.setModificationTime(tm().getTransactionTime())
.build()
diff --git a/core/src/test/java/google/registry/tools/AckPollMessagesCommandTest.java b/core/src/test/java/google/registry/tools/AckPollMessagesCommandTest.java
index c61f17be7..6d9406bb5 100644
--- a/core/src/test/java/google/registry/tools/AckPollMessagesCommandTest.java
+++ b/core/src/test/java/google/registry/tools/AckPollMessagesCommandTest.java
@@ -62,6 +62,7 @@ public class AckPollMessagesCommandTest extends CommandTestCase {
-
- private final DateTime now = DateTime.now(UTC);
- private DomainBase domain1;
- private DomainBase domain2;
- private HistoryEntry historyEntry1;
- private HistoryEntry historyEntry2;
- private BillingEvent.Recurring recurring1;
- private BillingEvent.Recurring recurring2;
-
- @BeforeEach
- void beforeEach() {
- createTld("tld");
- domain1 = persistActiveDomain("foo.tld");
- domain2 = persistActiveDomain("bar.tld");
- historyEntry1 =
- persistResource(
- new HistoryEntry.Builder().setParent(domain1).setModificationTime(now).build());
- historyEntry2 =
- persistResource(
- new HistoryEntry.Builder()
- .setParent(domain2)
- .setModificationTime(now.plusDays(1))
- .build());
- recurring1 =
- persistResource(
- new BillingEvent.Recurring.Builder()
- .setParent(historyEntry1)
- .setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
- .setReason(Reason.RENEW)
- .setEventTime(now.plusYears(1))
- .setRecurrenceEndTime(END_OF_TIME)
- .setClientId("a registrar")
- .setTargetId("foo.tld")
- .build());
- recurring2 =
- persistResource(
- new BillingEvent.Recurring.Builder()
- .setId(recurring1.getId())
- .setParent(historyEntry2)
- .setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
- .setReason(Reason.RENEW)
- .setEventTime(now.plusYears(1))
- .setRecurrenceEndTime(END_OF_TIME)
- .setClientId("a registrar")
- .setTargetId("bar.tld")
- .build());
- }
-
- @Test
- void testOnlyResaveBillingEventsCorrectly() throws Exception {
- assertThat(recurring1.getId()).isEqualTo(recurring2.getId());
-
- runCommand(
- "--force",
- "--key_paths_file",
- writeToNamedTmpFile("keypath.txt", getKeyPathLiteral(recurring1, recurring2)));
-
- assertNotChangeExceptUpdateTime(domain1, domain2, historyEntry1, historyEntry2);
- assertNotInDatastore(recurring1, recurring2);
-
- ImmutableList recurrings = loadAllRecurrings();
- assertThat(recurrings.size()).isEqualTo(2);
-
- recurrings.forEach(
- newRecurring -> {
- if (newRecurring.getTargetId().equals("foo.tld")) {
- assertSameRecurringEntityExceptId(newRecurring, recurring1);
- } else if (newRecurring.getTargetId().equals("bar.tld")) {
- assertSameRecurringEntityExceptId(newRecurring, recurring2);
- } else {
- fail("Unknown BillingEvent.Recurring entity: " + newRecurring.createVKey());
- }
- });
- }
-
- @Test
- void testResaveAssociatedDomainAndOneTimeBillingEventCorrectly() throws Exception {
- assertThat(recurring1.getId()).isEqualTo(recurring2.getId());
- GracePeriod gracePeriod =
- GracePeriod.createForRecurring(
- GracePeriodStatus.AUTO_RENEW,
- domain1.getRepoId(),
- now.plusDays(45),
- "a registrar",
- recurring1.createVKey());
- domain1 =
- persistResource(
- domain1
- .asBuilder()
- .setAutorenewBillingEvent(recurring1.createVKey())
- .setGracePeriods(ImmutableSet.of(gracePeriod))
- .setTransferData(
- new DomainTransferData.Builder()
- .setServerApproveAutorenewEvent(recurring1.createVKey())
- .setServerApproveEntities(ImmutableSet.of(recurring1.createVKey()))
- .build())
- .build());
-
- BillingEvent.OneTime oneTime =
- persistResource(
- new BillingEvent.OneTime.Builder()
- .setClientId("a registrar")
- .setTargetId("foo.tld")
- .setParent(historyEntry1)
- .setReason(Reason.CREATE)
- .setFlags(ImmutableSet.of(Flag.SYNTHETIC))
- .setSyntheticCreationTime(now)
- .setPeriodYears(2)
- .setCost(Money.of(USD, 1))
- .setEventTime(now)
- .setBillingTime(now.plusDays(5))
- .setCancellationMatchingBillingEvent(recurring1.createVKey())
- .build());
-
- runCommand(
- "--force",
- "--key_paths_file",
- writeToNamedTmpFile("keypath.txt", getKeyPathLiteral(recurring1, recurring2)));
-
- assertNotChangeExceptUpdateTime(domain2, historyEntry1, historyEntry2);
- assertNotInDatastore(recurring1, recurring2);
- ImmutableList recurrings = loadAllRecurrings();
- assertThat(recurrings.size()).isEqualTo(2);
-
- recurrings.forEach(
- newRecurring -> {
- if (newRecurring.getTargetId().equals("foo.tld")) {
- assertSameRecurringEntityExceptId(newRecurring, recurring1);
-
- BillingEvent.OneTime persistedOneTime = auditedOfy().load().entity(oneTime).now();
- assertAboutImmutableObjects()
- .that(persistedOneTime)
- .isEqualExceptFields(oneTime, "cancellationMatchingBillingEvent");
- assertThat(persistedOneTime.getCancellationMatchingBillingEvent())
- .isEqualTo(newRecurring.createVKey());
-
- DomainBase persistedDomain = auditedOfy().load().entity(domain1).now();
- assertAboutImmutableObjects()
- .that(persistedDomain)
- .isEqualExceptFields(
- domain1,
- "updateTimestamp",
- "revisions",
- "gracePeriods",
- "transferData",
- "autorenewBillingEvent");
- assertThat(persistedDomain.getAutorenewBillingEvent())
- .isEqualTo(newRecurring.createVKey());
- assertThat(persistedDomain.getGracePeriods())
- .containsExactly(
- GracePeriod.createForRecurring(
- GracePeriodStatus.AUTO_RENEW,
- domain1.getRepoId(),
- now.plusDays(45),
- "a registrar",
- newRecurring.createVKey(),
- gracePeriod.getGracePeriodId()));
- assertThat(persistedDomain.getTransferData().getServerApproveAutorenewEvent())
- .isEqualTo(newRecurring.createVKey());
- assertThat(persistedDomain.getTransferData().getServerApproveEntities())
- .containsExactly(newRecurring.createVKey());
-
- } else if (newRecurring.getTargetId().equals("bar.tld")) {
- assertSameRecurringEntityExceptId(newRecurring, recurring2);
- } else {
- fail("Unknown BillingEvent.Recurring entity: " + newRecurring.createVKey());
- }
- });
- }
-
- private static void assertNotInDatastore(ImmutableObject... entities) {
- for (ImmutableObject entity : entities) {
- assertThat(auditedOfy().load().entity(entity).now()).isNull();
- }
- }
-
- private static void assertNotChangeExceptUpdateTime(ImmutableObject... entities) {
- for (ImmutableObject entity : entities) {
- assertAboutImmutableObjects()
- .that(loadByEntity(entity))
- .isEqualExceptFields(entity, "updateTimestamp", "revisions");
- }
- }
-
- private static void assertSameRecurringEntityExceptId(
- BillingEvent.Recurring recurring1, BillingEvent.Recurring recurring2) {
- assertAboutImmutableObjects().that(recurring1).isEqualExceptFields(recurring2, "id");
- }
-
- private static ImmutableList loadAllRecurrings() {
- return ImmutableList.copyOf(auditedOfy().load().type(BillingEvent.Recurring.class));
- }
-
- private static String getKeyPathLiteral(Object... entities) {
- return Arrays.stream(entities)
- .map(
- entity -> {
- Key> key = Key.create(entity);
- return String.format(
- "\"DomainBase\", \"%s\", \"HistoryEntry\", %s, \"%s\", %s",
- key.getParent().getParent().getName(),
- key.getParent().getId(),
- key.getKind(),
- key.getId());
- })
- .reduce((k1, k2) -> k1 + "\n" + k2)
- .get();
- }
-}
diff --git a/core/src/test/java/google/registry/tools/UpdateDomainCommandTest.java b/core/src/test/java/google/registry/tools/UpdateDomainCommandTest.java
index 4674f9fed..38ae384c7 100644
--- a/core/src/test/java/google/registry/tools/UpdateDomainCommandTest.java
+++ b/core/src/test/java/google/registry/tools/UpdateDomainCommandTest.java
@@ -299,6 +299,7 @@ class UpdateDomainCommandTest extends EppToolCommandTestCase, HistoryEntry.Type>
+ HISTORY_ENTRY_CREATE_TYPES =
+ ImmutableMap.of(
+ DomainBase.class,
+ HistoryEntry.Type.DOMAIN_CREATE,
+ ContactResource.class,
+ HistoryEntry.Type.CONTACT_CREATE,
+ HostResource.class,
+ HistoryEntry.Type.HOST_CREATE);
+
@Test
void testKill() throws Exception {
createTld("tld1");
createTld("tld2");
- for (EppResource resource : asList(
- persistActiveDomain("foo.tld1"),
- persistActiveDomain("foo.tld2"),
- persistActiveContact("foo"),
- persistActiveContact("foo"),
- persistActiveHost("ns.foo.tld1"),
- persistActiveHost("ns.foo.tld2"))) {
- HistoryEntry history = new HistoryEntry.Builder().setParent(resource).build();
- for (ImmutableObject descendant : asList(
- history,
- new PollMessage.OneTime.Builder()
- .setParent(history)
- .setClientId("")
- .setEventTime(START_OF_TIME)
- .build(),
- new PollMessage.Autorenew.Builder()
- .setParent(history)
- .setClientId("")
- .setEventTime(START_OF_TIME)
- .build(),
- new BillingEvent.OneTime.Builder()
- .setParent(history)
- .setBillingTime(START_OF_TIME)
- .setEventTime(START_OF_TIME)
- .setClientId("")
- .setTargetId("")
- .setReason(Reason.CREATE)
- .setPeriodYears(1)
- .setCost(Money.of(CurrencyUnit.USD, 1))
- .build(),
- new BillingEvent.Recurring.Builder()
- .setParent(history)
- .setEventTime(START_OF_TIME)
- .setClientId("")
- .setTargetId("")
- .setReason(Reason.RENEW)
- .build())) {
+ for (EppResource resource :
+ asList(
+ persistActiveDomain("foo.tld1"),
+ persistActiveDomain("foo.tld2"),
+ persistActiveContact("foo"),
+ persistActiveContact("foo"),
+ persistActiveHost("ns.foo.tld1"),
+ persistActiveHost("ns.foo.tld2"))) {
+ HistoryEntry history =
+ new HistoryEntry.Builder()
+ .setParent(resource)
+ .setClientId(resource.getCreationClientId())
+ .setModificationTime(resource.getCreationTime())
+ .setType(HISTORY_ENTRY_CREATE_TYPES.get(resource.getClass()))
+ .build();
+ for (ImmutableObject descendant :
+ asList(
+ history,
+ new PollMessage.OneTime.Builder()
+ .setParent(history)
+ .setClientId("")
+ .setEventTime(START_OF_TIME)
+ .build(),
+ new PollMessage.Autorenew.Builder()
+ .setParent(history)
+ .setClientId("")
+ .setEventTime(START_OF_TIME)
+ .build(),
+ new BillingEvent.OneTime.Builder()
+ .setParent(history)
+ .setBillingTime(START_OF_TIME)
+ .setEventTime(START_OF_TIME)
+ .setClientId("")
+ .setTargetId("")
+ .setReason(Reason.CREATE)
+ .setPeriodYears(1)
+ .setCost(Money.of(CurrencyUnit.USD, 1))
+ .build(),
+ new BillingEvent.Recurring.Builder()
+ .setParent(history)
+ .setEventTime(START_OF_TIME)
+ .setClientId("")
+ .setTargetId("")
+ .setReason(Reason.RENEW)
+ .build())) {
persistResource(descendant);
}
}
diff --git a/core/src/test/java/google/registry/tools/server/ResaveAllHistoryEntriesActionTest.java b/core/src/test/java/google/registry/tools/server/ResaveAllHistoryEntriesActionTest.java
index 32dd9d9ca..034d0c23e 100644
--- a/core/src/test/java/google/registry/tools/server/ResaveAllHistoryEntriesActionTest.java
+++ b/core/src/test/java/google/registry/tools/server/ResaveAllHistoryEntriesActionTest.java
@@ -55,9 +55,25 @@ class ResaveAllHistoryEntriesActionTest extends MapreduceTestCase