Implement DatastoreEntity/SqlEntity for many more classes (#788)

* Implement DatastoreEntity/SqlEntity for many more classes

We still have many more classes to go, but this gets us closer to
guaranteeing that we can convert from Datastore to SQL objects and back
again.

* Shift SqlEntity impl to HistoryEntry
This commit is contained in:
gbrodman 2020-09-09 13:56:59 -04:00 committed by GitHub
parent a86fcf79f7
commit fb7ba80b86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 164 additions and 81 deletions

View file

@ -21,6 +21,7 @@ import static org.joda.time.DateTimeZone.UTC;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.common.CrossTldSingleton;
import google.registry.schema.replay.EntityTest.EntityForTesting;
import google.registry.testing.AppEngineExtension;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
@ -38,6 +39,7 @@ public class CreateAutoTimestampTest {
/** Timestamped class. */
@Entity(name = "CatTestEntity")
@EntityForTesting
public static class TestObject extends CrossTldSingleton {
CreateAutoTimestamp createTime = CreateAutoTimestamp.create(null);
}

View file

@ -29,6 +29,7 @@ import com.google.common.collect.Iterables;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.schema.replay.EntityTest.EntityForTesting;
import google.registry.testing.AppEngineExtension;
import google.registry.util.CidrAddressBlock;
import java.util.ArrayDeque;
@ -279,6 +280,7 @@ public class ImmutableObjectTest {
/** Simple subclass of ImmutableObject. */
@Entity
@EntityForTesting
public static class ValueObject extends ImmutableObject {
@Id
long id;

View file

@ -21,6 +21,7 @@ import static org.joda.time.DateTimeZone.UTC;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.common.CrossTldSingleton;
import google.registry.schema.replay.EntityTest.EntityForTesting;
import google.registry.testing.AppEngineExtension;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
@ -38,6 +39,7 @@ public class UpdateAutoTimestampTest {
/** Timestamped class. */
@Entity(name = "UatTestEntity")
@EntityForTesting
public static class UpdateAutoTimestampTestObject extends CrossTldSingleton {
UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create(null);
}

View file

@ -45,6 +45,7 @@ import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.eppcommon.Trid;
import google.registry.model.reporting.HistoryEntry;
import google.registry.schema.replay.EntityTest.EntityForTesting;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DatastoreHelper;
import google.registry.testing.FakeClock;
@ -69,13 +70,14 @@ public class OfyTest {
@BeforeEach
void beforeEach() {
createTld("tld");
someObject = new HistoryEntry.Builder()
.setClientId("client id")
.setModificationTime(START_OF_TIME)
.setParent(persistActiveContact("parentContact"))
.setTrid(Trid.create("client", "server"))
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.build();
someObject =
new HistoryEntry.Builder()
.setClientId("client id")
.setModificationTime(START_OF_TIME)
.setParent(persistActiveContact("parentContact"))
.setTrid(Trid.create("client", "server"))
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.build();
// This can't be initialized earlier because namespaces need the AppEngineRule to work.
}
@ -111,8 +113,7 @@ public class OfyTest {
assertThrows(
IllegalArgumentException.class,
() ->
tm()
.transact(
tm().transact(
() -> {
ofy().save().entity(someObject);
ofy().save().entity(someObject);
@ -126,8 +127,7 @@ public class OfyTest {
assertThrows(
IllegalArgumentException.class,
() ->
tm()
.transact(
tm().transact(
() -> {
ofy().delete().entity(someObject);
ofy().delete().entity(someObject);
@ -141,8 +141,7 @@ public class OfyTest {
assertThrows(
IllegalArgumentException.class,
() ->
tm()
.transact(
tm().transact(
() -> {
ofy().save().entity(someObject);
ofy().delete().entity(someObject);
@ -156,8 +155,7 @@ public class OfyTest {
assertThrows(
IllegalArgumentException.class,
() ->
tm()
.transact(
tm().transact(
() -> {
ofy().delete().entity(someObject);
ofy().save().entity(someObject);
@ -174,13 +172,12 @@ public class OfyTest {
/** Simple entity class with lifecycle callbacks. */
@com.googlecode.objectify.annotation.Entity
@EntityForTesting
public static class LifecycleObject extends ImmutableObject {
@Parent
Key<?> parent = getCrossTldKey();
@Parent Key<?> parent = getCrossTldKey();
@Id
long id = 1;
@Id long id = 1;
boolean onLoadCalled;
boolean onSaveCalled;
@ -218,20 +215,22 @@ public class OfyTest {
/** Avoid regressions of b/21309102 where transaction time did not change on each retry. */
@Test
void testTransact_getsNewTimestampOnEachTry() {
tm().transact(new Runnable() {
tm().transact(
new Runnable() {
DateTime firstAttemptTime;
DateTime firstAttemptTime;
@Override
public void run() {
if (firstAttemptTime == null) {
// Sleep a bit to ensure that the next attempt is at a new millisecond.
firstAttemptTime = tm().getTransactionTime();
sleepUninterruptibly(10, MILLISECONDS);
throw new ConcurrentModificationException();
}
assertThat(tm().getTransactionTime()).isGreaterThan(firstAttemptTime);
}});
@Override
public void run() {
if (firstAttemptTime == null) {
// Sleep a bit to ensure that the next attempt is at a new millisecond.
firstAttemptTime = tm().getTransactionTime();
sleepUninterruptibly(10, MILLISECONDS);
throw new ConcurrentModificationException();
}
assertThat(tm().getTransactionTime()).isGreaterThan(firstAttemptTime);
}
});
}
@Test
@ -319,17 +318,19 @@ public class OfyTest {
}
};
// A commit logged work that throws on the first attempt to get its result.
CommitLoggedWork<Void> commitLoggedWork = new CommitLoggedWork<Void>(work, new SystemClock()) {
boolean firstCallToGetResult = true;
CommitLoggedWork<Void> commitLoggedWork =
new CommitLoggedWork<Void>(work, new SystemClock()) {
boolean firstCallToGetResult = true;
@Override
public Void getResult() {
if (firstCallToGetResult) {
firstCallToGetResult = false;
throw new DatastoreTimeoutException("");
}
return null;
}};
@Override
public Void getResult() {
if (firstCallToGetResult) {
firstCallToGetResult = false;
throw new DatastoreTimeoutException("");
}
return null;
}
};
// Despite the DatastoreTimeoutException in the first call to getResult(), this should succeed
// without retrying. If a retry is triggered, the test should fail due to the call to fail().
ofy().transactCommitLoggedWork(commitLoggedWork);
@ -381,8 +382,7 @@ public class OfyTest {
void test_getBaseEntityClassFromEntityOrKey_subclassEntity() {
DomainBase domain = DatastoreHelper.newDomainBase("test.tld");
assertThat(getBaseEntityClassFromEntityOrKey(domain)).isEqualTo(DomainBase.class);
assertThat(getBaseEntityClassFromEntityOrKey(Key.create(domain)))
.isEqualTo(DomainBase.class);
assertThat(getBaseEntityClassFromEntityOrKey(Key.create(domain))).isEqualTo(DomainBase.class);
}
@Test

View file

@ -27,6 +27,7 @@ import com.googlecode.objectify.annotation.Entity;
import google.registry.model.common.CrossTldSingleton;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.Ofy;
import google.registry.schema.replay.EntityTest.EntityForTesting;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
@ -42,6 +43,7 @@ public class CommitLogRevisionsTranslatorFactoryTest {
private static final DateTime START_TIME = DateTime.parse("2000-01-01TZ");
@Entity(name = "ClrtfTestEntity")
@EntityForTesting
public static class TestObject extends CrossTldSingleton {
ImmutableSortedMap<DateTime, Key<CommitLogManifest>> revisions = ImmutableSortedMap.of();
}
@ -73,11 +75,11 @@ public class CommitLogRevisionsTranslatorFactoryTest {
@Test
void testSave_doesNotMutateOriginalResource() {
TestObject object = new TestObject();
save(object);
assertThat(object.revisions).isEmpty();
assertThat(reload().revisions).isNotEmpty();
}
TestObject object = new TestObject();
save(object);
assertThat(object.revisions).isEmpty();
assertThat(reload().revisions).isNotEmpty();
}
@Test
void testSave_translatorAddsKeyToCommitLogToField() {
@ -149,8 +151,10 @@ public class CommitLogRevisionsTranslatorFactoryTest {
com.google.appengine.api.datastore.Entity entity =
tm().transactNewReadOnly(() -> ofy().save().toEntity(reload()));
assertThat(entity.getProperties().keySet()).containsExactly("revisions.key", "revisions.value");
assertThat(entity.getProperties()).containsEntry(
"revisions.key", ImmutableList.of(START_TIME.toDate(), START_TIME.plusDays(1).toDate()));
assertThat(entity.getProperties())
.containsEntry(
"revisions.key",
ImmutableList.of(START_TIME.toDate(), START_TIME.plusDays(1).toDate()));
assertThat(entity.getProperty("revisions.value")).isInstanceOf(List.class);
assertThat(((List<Object>) entity.getProperty("revisions.value")).get(0))
.isInstanceOf(com.google.appengine.api.datastore.Key.class);

View file

@ -41,24 +41,32 @@ public class EntityTest {
new ClassGraph().enableAnnotationInfo().whitelistPackages("google.registry").scan()) {
// All javax.persistence entities must implement SqlEntity and vice versa
ImmutableSet<String> javaxPersistenceClasses =
getClassNames(
scanResult.getClassesWithAnnotation(javax.persistence.Entity.class.getName()));
getAllClassesWithAnnotation(scanResult, javax.persistence.Entity.class.getName());
ImmutableSet<String> sqlEntityClasses =
getClassNames(scanResult.getClassesImplementing(SqlEntity.class.getName()));
assertThat(javaxPersistenceClasses).isEqualTo(sqlEntityClasses);
assertThat(sqlEntityClasses).containsExactlyElementsIn(javaxPersistenceClasses);
// All com.googlecode.objectify.annotation.Entity classes must implement DatastoreEntity and
// vice versa
// All com.googlecode.objectify entities must implement DatastoreEntity and vice versa
ImmutableSet<String> objectifyClasses =
getClassNames(
scanResult.getClassesWithAnnotation(
com.googlecode.objectify.annotation.Entity.class.getName()));
getAllClassesWithAnnotation(
scanResult, com.googlecode.objectify.annotation.Entity.class.getName());
ImmutableSet<String> datastoreEntityClasses =
getClassNames(scanResult.getClassesImplementing(DatastoreEntity.class.getName()));
assertThat(objectifyClasses).isEqualTo(datastoreEntityClasses);
assertThat(datastoreEntityClasses).containsExactlyElementsIn(objectifyClasses);
}
}
private ImmutableSet<String> getAllClassesWithAnnotation(
ScanResult scanResult, String annotation) {
ImmutableSet.Builder<String> result = new ImmutableSet.Builder<>();
ClassInfoList classesWithAnnotation = scanResult.getClassesWithAnnotation(annotation);
result.addAll(getClassNames(classesWithAnnotation));
classesWithAnnotation.stream()
.map(ClassInfo::getSubclasses)
.forEach(classInfoList -> result.addAll(getClassNames(classInfoList)));
return result.build();
}
private ImmutableSet<String> getClassNames(ClassInfoList classInfoList) {
return classInfoList.stream()
.filter(ClassInfo::isStandardClass)

View file

@ -23,11 +23,11 @@ import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.VirtualEntity;
import google.registry.model.common.EntityGroupRoot;
import google.registry.schema.replay.EntityTest.EntityForTesting;
/**
* A test model object that can be persisted in any entity group.
*/
/** A test model object that can be persisted in any entity group. */
@Entity
@EntityForTesting
public class TestObject extends ImmutableObject {
@Parent
@ -65,6 +65,7 @@ public class TestObject extends ImmutableObject {
/** A test @VirtualEntity model object, which should not be persisted. */
@Entity
@VirtualEntity
@EntityForTesting
public static class TestVirtualObject extends ImmutableObject {
@Id