Change @DoNotHydrate to work on fields, not types.

There was a circular reference when hydrating a domain with a
subordinate host, since the host references the domain. To fix
this, I redid @DoNotHydrate to be the way it should have been,
rather than the hack I had originally submitted. I also beefed
up the unit tests of the epp resource types to check for cycles.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=135792416
This commit is contained in:
cgoldfeder 2016-10-11 07:05:26 -07:00 committed by Ben McIlwain
parent 27ec47051e
commit cb8320ff40
12 changed files with 151 additions and 118 deletions

View file

@ -31,7 +31,6 @@ import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.model.ImmutableObject.DoNotHydrate;
import google.registry.model.domain.ReferenceUnion;
import google.registry.testing.AppEngineRule;
import google.registry.util.CidrAddressBlock;
@ -59,8 +58,7 @@ public class ImmutableObjectTest {
@Before
public void register() {
ObjectifyService.register(HydratableObject.class);
ObjectifyService.register(UnhydratableObject.class);
ObjectifyService.register(ValueObject.class);
}
/** Simple subclass of ImmutableObject. */
@ -257,81 +255,64 @@ public class ImmutableObjectTest {
/** Subclass of ImmutableObject with keys to other objects. */
public static class RootObject extends ImmutableObject {
Key<HydratableObject> hydratable;
Key<ValueObject> hydrateMe;
Key<UnhydratableObject> unhydratable;
@DoNotHydrate
Key<ValueObject> skipMe;
Map<String, Key<?>> map;
Map<String, Key<ValueObject>> map;
Set<Key<?>> set;
Set<Key<ValueObject>> set;
ReferenceUnion<?> referenceUnion;
}
/** Hydratable subclass of ImmutableObject. */
/** Simple subclass of ImmutableObject. */
@Entity
public static class HydratableObject extends ImmutableObject {
public static class ValueObject extends ImmutableObject {
@Id
long id = 1;
long id;
String value;
}
/** Unhydratable subclass of SimpleObject. */
@Entity
@DoNotHydrate
public static class UnhydratableObject extends ImmutableObject {
@Id
long id = 1;
String value;
static ValueObject create(long id, String value) {
ValueObject instance = new ValueObject();
instance.id = id;
instance.value = value;
return instance;
}
}
@Test
public void testToHydratedString_skipsDoNotHydrate() {
HydratableObject hydratable = new HydratableObject();
hydratable.value = "expected";
UnhydratableObject unhydratable = new UnhydratableObject();
unhydratable.value = "unexpected";
RootObject root = new RootObject();
root.hydratable = Key.create(persistResource(hydratable));
root.unhydratable = Key.create(persistResource(unhydratable));
assertThat(root.toHydratedString()).contains("expected");
assertThat(root.toHydratedString()).doesNotContain("unexpected");
root.hydrateMe = Key.create(persistResource(ValueObject.create(1, "foo")));
root.skipMe = Key.create(persistResource(ValueObject.create(2, "bar")));
String hydratedString = root.toHydratedString();
assertThat(hydratedString).contains("foo");
assertThat(hydratedString).doesNotContain("bar");
}
@Test
public void testToHydratedString_expandsMaps() {
HydratableObject hydratable = new HydratableObject();
hydratable.value = "expected";
UnhydratableObject unhydratable = new UnhydratableObject();
unhydratable.value = "unexpected";
RootObject root = new RootObject();
root.map = ImmutableMap.<String, Key<?>>of(
"hydratable", Key.create(persistResource(hydratable)),
"unhydratable", Key.create(persistResource(unhydratable)));
assertThat(root.toHydratedString()).contains("expected");
assertThat(root.toHydratedString()).doesNotContain("unexpected");
root.map = ImmutableMap.of("foo", Key.create(persistResource(ValueObject.create(1, "bar"))));
String hydratedString = root.toHydratedString();
assertThat(hydratedString).contains("foo");
assertThat(hydratedString).contains("bar");
}
@Test
public void testToHydratedString_expandsCollections() {
HydratableObject hydratable = new HydratableObject();
hydratable.value = "expected";
UnhydratableObject unhydratable = new UnhydratableObject();
unhydratable.value = "unexpected";
RootObject root = new RootObject();
root.set = ImmutableSet.<Key<?>>of(
Key.create(persistResource(hydratable)),
Key.create(persistResource(unhydratable)));
assertThat(root.toHydratedString()).contains("expected");
assertThat(root.toHydratedString()).doesNotContain("unexpected");
root.set = ImmutableSet.of(Key.create(persistResource(ValueObject.create(1, "foo"))));
assertThat(root.toHydratedString()).contains("foo");
}
@Test
public void testToHydratedString_expandsReferenceUnions() {
RootObject root = new RootObject();
root.referenceUnion = ReferenceUnion.create(Key.create(persistActiveContact("expected")));
assertThat(root.toHydratedString()).contains("expected");
root.referenceUnion = ReferenceUnion.create(Key.create(persistActiveContact("foo")));
assertThat(root.toHydratedString()).contains("foo");
}
}

View file

@ -110,17 +110,21 @@ public class ModelUtilsTest {
testInstance.id = "foo";
testInstance.a = "a";
testInstance.b = "b";
// More complicated version of isEqualTo() so that we check for ordering.
assertThat(ModelUtils.getFieldValues(testInstance).entrySet())
.containsExactlyElementsIn(ImmutableMap.of("id", "foo", "a", "a", "b", "b").entrySet())
assertThat(ModelUtils.getFieldValues(testInstance))
.containsExactly(
TestClass.class.getDeclaredField("id"), "foo",
TestClass.class.getDeclaredField("a"), "a",
TestClass.class.getDeclaredField("b"), "b")
.inOrder();
// Test again, to make sure we aren't caching values.
testInstance.id = "bar";
testInstance.a = "1";
testInstance.b = "2";
// More complicated version of isEqualTo() so that we check for ordering.
assertThat(ModelUtils.getFieldValues(testInstance).entrySet())
.containsExactlyElementsIn(ImmutableMap.of("id", "bar", "a", "1", "b", "2").entrySet())
assertThat(ModelUtils.getFieldValues(testInstance))
.containsExactly(
TestClass.class.getDeclaredField("id"), "bar",
TestClass.class.getDeclaredField("a"), "1",
TestClass.class.getDeclaredField("b"), "2")
.inOrder();
}

View file

@ -227,4 +227,10 @@ public class ContactResourceTest extends EntityTestCase {
thrown.expect(IllegalStateException.class, "creationTime can only be set once");
contactResource.asBuilder().setCreationTime(END_OF_TIME);
}
@Test
public void testToHydratedString_notCircular() {
// If there are circular references, this will overflow the stack.
contactResource.toHydratedString();
}
}

View file

@ -179,4 +179,10 @@ public class DomainApplicationTest extends EntityTestCase {
assertThat(withNull).isEqualTo(withEmpty);
assertThat(withEmpty.hasTransferData()).isFalse();
}
@Test
public void testToHydratedString_notCircular() {
// If there are circular references, this will overflow the stack.
domainApplication.toHydratedString();
}
}

View file

@ -41,6 +41,7 @@ import google.registry.model.EntityTestCase;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData;
@ -79,26 +80,38 @@ public class DomainResourceTest extends EntityTestCase {
@Before
public void setUp() throws Exception {
createTld("com");
HostResource hostResource = persistResource(
Key<DomainResource> domainKey = Key.create(null, DomainResource.class, "4-COM");
Key<HostResource> hostKey = Key.create(persistResource(
new HostResource.Builder()
.setFullyQualifiedHostName("ns1.example.com")
.setSuperordinateDomain(domainKey)
.setRepoId("1-COM")
.build());
ContactResource contactResource1 = persistResource(
.build()));
Key<ContactResource> contact1Key = Key.create(persistResource(
new ContactResource.Builder()
.setContactId("contact_id1")
.setRepoId("2-COM")
.build());
ContactResource contactResource2 = persistResource(
.build()));
Key<ContactResource> contact2Key = Key.create(persistResource(
new ContactResource.Builder()
.setContactId("contact_id2")
.setRepoId("3-COM")
.build());
.build()));
Key<HistoryEntry> historyEntryKey =
Key.create(persistResource(new HistoryEntry.Builder().setParent(domainKey).build()));
Key<BillingEvent.OneTime> oneTimeBillKey =
Key.create(historyEntryKey, BillingEvent.OneTime.class, 1);
Key<BillingEvent.Recurring> recurringBillKey =
Key.create(historyEntryKey, BillingEvent.Recurring.class, 2);
Key<PollMessage.Autorenew> autorenewPollKey =
Key.create(historyEntryKey, PollMessage.Autorenew.class, 3);
Key<PollMessage.OneTime> onetimePollKey =
Key.create(historyEntryKey, PollMessage.OneTime.class, 1);
// Set up a new persisted domain entity.
domain = cloneAndSetAutoTimestamps(
new DomainResource.Builder()
.setFullyQualifiedDomainName("example.com")
.setRepoId("4-COM")
.setRepoId("4-COM")
.setCreationClientId("a registrar")
.setLastEppUpdateTime(clock.nowUtc())
.setLastEppUpdateClientId("AnotherRegistrar")
@ -110,11 +123,9 @@ public class DomainResourceTest extends EntityTestCase {
StatusValue.SERVER_UPDATE_PROHIBITED,
StatusValue.SERVER_RENEW_PROHIBITED,
StatusValue.SERVER_HOLD))
.setRegistrant(Key.create(contactResource1))
.setContacts(ImmutableSet.of(DesignatedContact.create(
DesignatedContact.Type.ADMIN,
Key.create(contactResource2))))
.setNameservers(ImmutableSet.of(Key.create(hostResource)))
.setRegistrant(contact1Key)
.setContacts(ImmutableSet.of(DesignatedContact.create(Type.ADMIN, contact2Key)))
.setNameservers(ImmutableSet.of(hostKey))
.setSubordinateHosts(ImmutableSet.of("ns1.example.com"))
.setCurrentSponsorClientId("ThirdRegistrar")
.setRegistrationExpirationTime(clock.nowUtc().plusYears(1))
@ -130,22 +141,19 @@ public class DomainResourceTest extends EntityTestCase {
.setPendingTransferExpirationTime(clock.nowUtc())
.setServerApproveEntities(
ImmutableSet.<Key<? extends TransferServerApproveEntity>>of(
Key.create(BillingEvent.OneTime.class, 1),
Key.create(BillingEvent.Recurring.class, 2),
Key.create(PollMessage.Autorenew.class, 3)))
.setServerApproveBillingEvent(
Key.create(BillingEvent.OneTime.class, 1))
.setServerApproveAutorenewEvent(
Key.create(BillingEvent.Recurring.class, 2))
.setServerApproveAutorenewPollMessage(
Key.create(PollMessage.Autorenew.class, 3))
oneTimeBillKey,
recurringBillKey,
autorenewPollKey))
.setServerApproveBillingEvent(oneTimeBillKey)
.setServerApproveAutorenewEvent(recurringBillKey)
.setServerApproveAutorenewPollMessage(autorenewPollKey)
.setTransferRequestTime(clock.nowUtc().plusDays(1))
.setTransferStatus(TransferStatus.SERVER_APPROVED)
.setTransferRequestTrid(Trid.create("client trid"))
.build())
.setDeletePollMessage(Key.create(PollMessage.OneTime.class, 1))
.setAutorenewBillingEvent(Key.create(BillingEvent.Recurring.class, 1))
.setAutorenewPollMessage(Key.create(PollMessage.Autorenew.class, 2))
.setDeletePollMessage(onetimePollKey)
.setAutorenewBillingEvent(recurringBillKey)
.setAutorenewPollMessage(autorenewPollKey)
.setSmdId("smdid")
.setApplicationTime(START_OF_TIME)
.setApplication(Key.create(DomainApplication.class, 1))
@ -439,4 +447,9 @@ public class DomainResourceTest extends EntityTestCase {
// Assert that there was only one call to datastore (that may have loaded many keys).
assertThat(skip(RequestCapturingAsyncDatastoreService.getReads(), numPreviousReads)).hasSize(1);
}
@Test
public void testToHydratedString_notCircular() {
domain.toHydratedString(); // If there are circular references, this will overflow the stack.
}
}

View file

@ -284,4 +284,10 @@ public class HostResourceTest extends EntityTestCase {
assertThat(afterTransfer.getCurrentSponsorClientId()).isEqualTo("winner");
assertThat(afterTransfer.getLastTransferTime()).isEqualTo(clock.nowUtc().plusDays(1));
}
@Test
public void testToHydratedString_notCircular() {
// If there are circular references, this will overflow the stack.
hostResource.toHydratedString();
}
}