Use TransactionManager APIs in DatastoreHelper (#849)

* Make DatastoreHelper support Postgresql

* Rebase on HEAD

* Resolve comments

* Use put* inside insert* and update*

* Resolve comments
This commit is contained in:
Shicong Huang 2020-10-29 11:41:04 -04:00 committed by GitHub
parent db2e896d42
commit 1e51f51979
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 979 additions and 299 deletions

View file

@ -480,8 +480,7 @@ public class ExpandRecurringBillingEventsActionTest
.setSyntheticCreationTime(testTime)
.build());
}
assertBillingEventsForResource(
domain, Iterables.toArray(expectedEvents, BillingEvent.class));
assertBillingEventsForResource(domain, Iterables.toArray(expectedEvents, BillingEvent.class));
assertCursorAt(testTime);
}

View file

@ -17,11 +17,12 @@ package google.registry.model.billing;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.ofyTmOrDoNothing;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static org.joda.money.CurrencyUnit.USD;
import static org.joda.time.DateTimeZone.UTC;
@ -39,13 +40,17 @@ import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.util.DateTimeUtils;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link BillingEvent}. */
@DualDatabaseTest
public class BillingEventTest extends EntityTestCase {
private final DateTime now = DateTime.now(UTC);
@ -67,16 +72,26 @@ public class BillingEventTest extends EntityTestCase {
void setUp() {
createTld("tld");
domain = persistActiveDomain("foo.tld");
historyEntry = persistResource(
new HistoryEntry.Builder()
.setParent(domain)
.setModificationTime(now)
.build());
historyEntry2 = persistResource(
new HistoryEntry.Builder()
.setParent(domain)
.setModificationTime(now.plusDays(1))
.build());
historyEntry =
persistResource(
new HistoryEntry.Builder()
.setParent(domain)
.setModificationTime(now)
.setRequestedByRegistrar(false)
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setXmlBytes(new byte[0])
.build()
.toChildHistoryEntity());
historyEntry2 =
persistResource(
new HistoryEntry.Builder()
.setParent(domain)
.setModificationTime(now.plusDays(1))
.setRequestedByRegistrar(false)
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setXmlBytes(new byte[0])
.build()
.toChildHistoryEntity());
AllocationToken allocationToken =
persistResource(
@ -149,74 +164,38 @@ public class BillingEventTest extends EntityTestCase {
.setEventTime(now.plusDays(1))
.setBillingTime(now.plusYears(1).plusDays(45))
.setRecurringEventKey(recurring.createVKey())));
modification = persistResource(commonInit(
new BillingEvent.Modification.Builder()
.setParent(historyEntry2)
.setReason(Reason.CREATE)
.setCost(Money.of(USD, 1))
.setDescription("Something happened")
.setEventTime(now.plusDays(1))
.setEventKey(Key.create(oneTime))));
modification =
ofyTmOrDoNothing(
() ->
persistResource(
commonInit(
new BillingEvent.Modification.Builder()
.setParent(historyEntry2)
.setReason(Reason.CREATE)
.setCost(Money.of(USD, 1))
.setDescription("Something happened")
.setEventTime(now.plusDays(1))
.setEventKey(Key.create(oneTime)))));
}
private <E extends BillingEvent, B extends BillingEvent.Builder<E, B>> E commonInit(B builder) {
return builder
.setClientId("a registrar")
.setTargetId("foo.tld")
.build();
return builder.setClientId("TheRegistrar").setTargetId("foo.tld").build();
}
private void saveNewBillingEvent(BillingEvent billingEvent) {
jpaTm().transact(() -> jpaTm().insert(billingEvent));
}
@Test
void testCloudSqlPersistence_OneTime() {
saveRegistrar("a registrar");
saveNewBillingEvent(oneTime);
BillingEvent.OneTime persisted = jpaTm().transact(() -> jpaTm().load(oneTime.createVKey()));
// TODO(b/168325240): Remove this fix after VKeyConverter generates symmetric key for
// AllocationToken.
BillingEvent.OneTime fixed =
persisted.asBuilder().setAllocationToken(oneTime.getAllocationToken().get()).build();
assertThat(fixed).isEqualTo(oneTime);
}
@Test
void testCloudSqlPersistence_Cancellation() {
saveRegistrar("a registrar");
saveNewBillingEvent(oneTime);
saveNewBillingEvent(cancellationOneTime);
BillingEvent.Cancellation persisted =
jpaTm().transact(() -> jpaTm().load(cancellationOneTime.createVKey()));
// TODO(b/168537779): Remove this fix after VKey<OneTime> can be reconstructed correctly.
BillingEvent.Cancellation fixed =
persisted.asBuilder().setOneTimeEventKey(oneTime.createVKey()).build();
assertThat(fixed).isEqualTo(cancellationOneTime);
}
@Test
void testCloudSqlPersistence_Recurring() {
saveRegistrar("a registrar");
saveNewBillingEvent(recurring);
BillingEvent.Recurring persisted = jpaTm().transact(() -> jpaTm().load(recurring.createVKey()));
assertThat(persisted).isEqualTo(recurring);
}
@Test
@TestOfyAndSql
void testPersistence() {
assertThat(ofy().load().entity(oneTime).now()).isEqualTo(oneTime);
assertThat(ofy().load().entity(oneTimeSynthetic).now()).isEqualTo(oneTimeSynthetic);
assertThat(ofy().load().entity(recurring).now()).isEqualTo(recurring);
assertThat(ofy().load().entity(cancellationOneTime).now()).isEqualTo(cancellationOneTime);
assertThat(ofy().load().entity(cancellationRecurring).now()).isEqualTo(cancellationRecurring);
assertThat(ofy().load().entity(modification).now()).isEqualTo(modification);
assertThat(transactIfJpaTm(() -> tm().load(oneTime))).isEqualTo(oneTime);
assertThat(transactIfJpaTm(() -> tm().load(oneTimeSynthetic))).isEqualTo(oneTimeSynthetic);
assertThat(transactIfJpaTm(() -> tm().load(recurring))).isEqualTo(recurring);
assertThat(transactIfJpaTm(() -> tm().load(cancellationOneTime)))
.isEqualTo(cancellationOneTime);
assertThat(transactIfJpaTm(() -> tm().load(cancellationRecurring)))
.isEqualTo(cancellationRecurring);
ofyTmOrDoNothing(() -> assertThat(tm().load(modification)).isEqualTo(modification));
}
@Test
@TestOfyOnly
void testParenting() {
// Note that these are all tested separately because BillingEvent is an abstract base class that
// lacks the @Entity annotation, and thus we cannot call .type(BillingEvent.class)
@ -238,19 +217,14 @@ public class BillingEventTest extends EntityTestCase {
.containsExactly(modification);
}
@Test
@TestOfyAndSql
void testCancellationMatching() {
Key<?> recurringKey =
ofy()
.load()
.entity(oneTimeSynthetic)
.now()
.getCancellationMatchingBillingEvent()
.getOfyKey();
assertThat(ofy().load().key(recurringKey).now()).isEqualTo(recurring);
VKey<?> recurringKey =
transactIfJpaTm(() -> tm().load(oneTimeSynthetic).getCancellationMatchingBillingEvent());
assertThat(transactIfJpaTm(() -> tm().load(recurringKey))).isEqualTo(recurring);
}
@Test
@TestOfyOnly
void testIndexing() throws Exception {
verifyIndexing(
oneTime,
@ -272,7 +246,7 @@ public class BillingEventTest extends EntityTestCase {
verifyIndexing(modification, "clientId", "eventTime");
}
@Test
@TestOfyAndSql
void testFailure_syntheticFlagWithoutCreationTime() {
IllegalStateException thrown =
assertThrows(
@ -288,7 +262,7 @@ public class BillingEventTest extends EntityTestCase {
.contains("Synthetic creation time must be set if and only if the SYNTHETIC flag is set.");
}
@Test
@TestOfyAndSql
void testFailure_syntheticCreationTimeWithoutFlag() {
IllegalStateException thrown =
assertThrows(
@ -299,7 +273,7 @@ public class BillingEventTest extends EntityTestCase {
.contains("Synthetic creation time must be set if and only if the SYNTHETIC flag is set");
}
@Test
@TestOfyAndSql
void testFailure_syntheticFlagWithoutCancellationMatchingKey() {
IllegalStateException thrown =
assertThrows(
@ -317,7 +291,7 @@ public class BillingEventTest extends EntityTestCase {
+ "if and only if the SYNTHETIC flag is set");
}
@Test
@TestOfyAndSql
void testFailure_cancellationMatchingKeyWithoutFlag() {
IllegalStateException thrown =
assertThrows(
@ -334,7 +308,7 @@ public class BillingEventTest extends EntityTestCase {
+ "if and only if the SYNTHETIC flag is set");
}
@Test
@TestOfyAndSql
void testSuccess_cancellation_forGracePeriod_withOneTime() {
BillingEvent.Cancellation newCancellation =
BillingEvent.Cancellation.forGracePeriod(
@ -343,10 +317,15 @@ public class BillingEventTest extends EntityTestCase {
"foo.tld");
// Set ID to be the same to ignore for the purposes of comparison.
newCancellation = newCancellation.asBuilder().setId(cancellationOneTime.getId()).build();
assertThat(newCancellation).isEqualTo(cancellationOneTime);
// TODO(b/168537779): Remove setRecurringEventKey after symmetric VKey can be reconstructed
// correctly.
assertThat(newCancellation)
.isEqualTo(
cancellationOneTime.asBuilder().setOneTimeEventKey(oneTime.createVKey()).build());
}
@Test
@TestOfyAndSql
void testSuccess_cancellation_forGracePeriod_withRecurring() {
BillingEvent.Cancellation newCancellation =
BillingEvent.Cancellation.forGracePeriod(
@ -354,16 +333,21 @@ public class BillingEventTest extends EntityTestCase {
GracePeriodStatus.AUTO_RENEW,
domain.getRepoId(),
now.plusYears(1).plusDays(45),
"a registrar",
"TheRegistrar",
recurring.createVKey()),
historyEntry2,
"foo.tld");
// Set ID to be the same to ignore for the purposes of comparison.
newCancellation = newCancellation.asBuilder().setId(cancellationRecurring.getId()).build();
assertThat(newCancellation).isEqualTo(cancellationRecurring);
// TODO(b/168537779): Remove setRecurringEventKey after symmetric VKey can be reconstructed
// correctly.
assertThat(newCancellation)
.isEqualTo(
cancellationRecurring.asBuilder().setRecurringEventKey(recurring.createVKey()).build());
}
@Test
@TestOfyAndSql
void testFailure_cancellation_forGracePeriodWithoutBillingEvent() {
IllegalArgumentException thrown =
assertThrows(
@ -380,7 +364,7 @@ public class BillingEventTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().contains("grace period without billing event");
}
@Test
@TestOfyAndSql
void testFailure_cancellationWithNoBillingEvent() {
IllegalStateException thrown =
assertThrows(
@ -394,7 +378,7 @@ public class BillingEventTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().contains("exactly one billing event");
}
@Test
@TestOfyAndSql
void testFailure_cancellationWithBothBillingEvents() {
IllegalStateException thrown =
assertThrows(
@ -408,7 +392,7 @@ public class BillingEventTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().contains("exactly one billing event");
}
@Test
@TestOfyAndSql
void testDeadCodeThatDeletedScrapCommandsReference() {
assertThat(recurring.getParentKey()).isEqualTo(Key.create(historyEntry));
new BillingEvent.OneTime.Builder().setParent(Key.create(historyEntry));

View file

@ -23,9 +23,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.EntityTestCase;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestTemplate;
/** Unit tests for {@link RdeRevision}. */
@DualDatabaseTest
@ -40,20 +40,20 @@ public class RdeRevisionTest extends EntityTestCase {
fakeClock.setTo(DateTime.parse("1984-12-18TZ"));
}
@TestTemplate
@TestOfyAndSql
void testGetNextRevision_objectDoesntExist_returnsZero() {
tm().transact(
() -> assertThat(getNextRevision("torment", fakeClock.nowUtc(), FULL)).isEqualTo(0));
}
@TestTemplate
@TestOfyAndSql
void testGetNextRevision_objectExistsAtZero_returnsOne() {
save("sorrow", fakeClock.nowUtc(), FULL, 0);
tm().transact(
() -> assertThat(getNextRevision("sorrow", fakeClock.nowUtc(), FULL)).isEqualTo(1));
}
@TestTemplate
@TestOfyAndSql
void testSaveRevision_objectDoesntExist_newRevisionIsZero_nextRevIsOne() {
tm().transact(() -> saveRevision("despondency", fakeClock.nowUtc(), FULL, 0));
tm().transact(
@ -61,7 +61,7 @@ public class RdeRevisionTest extends EntityTestCase {
assertThat(getNextRevision("despondency", fakeClock.nowUtc(), FULL)).isEqualTo(1));
}
@TestTemplate
@TestOfyAndSql
void testSaveRevision_objectDoesntExist_newRevisionIsOne_throwsVe() {
IllegalArgumentException thrown =
assertThrows(
@ -74,7 +74,7 @@ public class RdeRevisionTest extends EntityTestCase {
+ "when trying to save new revision 1");
}
@TestTemplate
@TestOfyAndSql
void testSaveRevision_objectExistsAtZero_newRevisionIsZero_throwsVe() {
save("melancholy", fakeClock.nowUtc(), FULL, 0);
IllegalArgumentException thrown =
@ -84,7 +84,7 @@ public class RdeRevisionTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().contains("object already created");
}
@TestTemplate
@TestOfyAndSql
void testSaveRevision_objectExistsAtZero_newRevisionIsOne_nextRevIsTwo() {
DateTime startOfDay = fakeClock.nowUtc().withTimeAtStartOfDay();
save("melancholy", startOfDay, FULL, 0);
@ -93,7 +93,7 @@ public class RdeRevisionTest extends EntityTestCase {
tm().transact(() -> assertThat(getNextRevision("melancholy", startOfDay, FULL)).isEqualTo(2));
}
@TestTemplate
@TestOfyAndSql
void testSaveRevision_objectExistsAtZero_newRevisionIsTwo_throwsVe() {
save("melancholy", fakeClock.nowUtc(), FULL, 0);
IllegalArgumentException thrown =
@ -105,7 +105,7 @@ public class RdeRevisionTest extends EntityTestCase {
.contains("RDE revision object should be at revision 1 but was");
}
@TestTemplate
@TestOfyAndSql
void testSaveRevision_negativeRevision_throwsIae() {
IllegalArgumentException thrown =
assertThrows(
@ -114,7 +114,7 @@ public class RdeRevisionTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().contains("Negative revision");
}
@TestTemplate
@TestOfyAndSql
void testSaveRevision_callerNotInTransaction_throwsIse() {
IllegalStateException thrown =
assertThrows(

View file

@ -33,6 +33,8 @@ import google.registry.testing.AppEngineExtension;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
@ -40,7 +42,6 @@ import java.util.stream.Stream;
import javax.persistence.Embeddable;
import javax.persistence.MappedSuperclass;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.RegisterExtension;
/**
@ -78,28 +79,28 @@ public class TransactionManagerTest {
fakeClock.advanceOneMilli();
}
@TestTemplate
@TestOfyAndSql
void inTransaction_returnsCorrespondingResult() {
assertThat(tm().inTransaction()).isFalse();
tm().transact(() -> assertThat(tm().inTransaction()).isTrue());
assertThat(tm().inTransaction()).isFalse();
}
@TestTemplate
@TestOfyAndSql
void assertInTransaction_throwsExceptionWhenNotInTransaction() {
assertThrows(IllegalStateException.class, () -> tm().assertInTransaction());
tm().transact(() -> tm().assertInTransaction());
assertThrows(IllegalStateException.class, () -> tm().assertInTransaction());
}
@TestTemplate
@TestOfyAndSql
void getTransactionTime_throwsExceptionWhenNotInTransaction() {
assertThrows(IllegalStateException.class, () -> tm().getTransactionTime());
tm().transact(() -> assertThat(tm().getTransactionTime()).isEqualTo(fakeClock.nowUtc()));
assertThrows(IllegalStateException.class, () -> tm().getTransactionTime());
}
@TestTemplate
@TestOfyAndSql
void transact_hasNoEffectWithPartialSuccess() {
assertEntityNotExist(theEntity);
assertThrows(
@ -113,21 +114,21 @@ public class TransactionManagerTest {
assertEntityNotExist(theEntity);
}
@TestTemplate
@TestOfyAndSql
void transact_reusesExistingTransaction() {
assertEntityNotExist(theEntity);
tm().transact(() -> tm().transact(() -> tm().insert(theEntity)));
assertEntityExists(theEntity);
}
@TestTemplate
@TestOfyAndSql
void transactNew_succeeds() {
assertEntityNotExist(theEntity);
tm().transactNew(() -> tm().insert(theEntity));
assertEntityExists(theEntity);
}
@TestTemplate
@TestOfyAndSql
void transactNewReadOnly_succeeds() {
assertEntityNotExist(theEntity);
tm().transact(() -> tm().insert(theEntity));
@ -136,7 +137,7 @@ public class TransactionManagerTest {
assertThat(persisted).isEqualTo(theEntity);
}
@TestTemplate
@TestOfyAndSql
void transactNewReadOnly_throwsWhenWritingEntity() {
assertEntityNotExist(theEntity);
assertThrows(
@ -144,7 +145,7 @@ public class TransactionManagerTest {
assertEntityNotExist(theEntity);
}
@TestTemplate
@TestOfyAndSql
void saveNew_succeeds() {
assertEntityNotExist(theEntity);
tm().transact(() -> tm().insert(theEntity));
@ -152,14 +153,14 @@ public class TransactionManagerTest {
assertThat(tm().transact(() -> tm().load(theEntity.key()))).isEqualTo(theEntity);
}
@TestTemplate
@TestOfyAndSql
void saveAllNew_succeeds() {
assertAllEntitiesNotExist(moreEntities);
tm().transact(() -> tm().insertAll(moreEntities));
assertAllEntitiesExist(moreEntities);
}
@TestTemplate
@TestOfyAndSql
void saveNewOrUpdate_persistsNewEntity() {
assertEntityNotExist(theEntity);
tm().transact(() -> tm().put(theEntity));
@ -167,7 +168,7 @@ public class TransactionManagerTest {
assertThat(tm().transact(() -> tm().load(theEntity.key()))).isEqualTo(theEntity);
}
@TestTemplate
@TestOfyAndSql
void saveNewOrUpdate_updatesExistingEntity() {
tm().transact(() -> tm().insert(theEntity));
TestEntity persisted = tm().transact(() -> tm().load(theEntity.key()));
@ -179,14 +180,14 @@ public class TransactionManagerTest {
assertThat(persisted.data).isEqualTo("bar");
}
@TestTemplate
@TestOfyAndSql
void saveNewOrUpdateAll_succeeds() {
assertAllEntitiesNotExist(moreEntities);
tm().transact(() -> tm().putAll(moreEntities));
assertAllEntitiesExist(moreEntities);
}
@TestTemplate
@TestOfyAndSql
void update_succeeds() {
tm().transact(() -> tm().insert(theEntity));
TestEntity persisted =
@ -202,7 +203,7 @@ public class TransactionManagerTest {
assertThat(persisted.data).isEqualTo("bar");
}
@TestTemplate
@TestOfyAndSql
void load_succeeds() {
assertEntityNotExist(theEntity);
tm().transact(() -> tm().insert(theEntity));
@ -211,14 +212,14 @@ public class TransactionManagerTest {
assertThat(persisted.data).isEqualTo("foo");
}
@TestTemplate
@TestOfyAndSql
void load_throwsOnMissingElement() {
assertEntityNotExist(theEntity);
assertThrows(
NoSuchElementException.class, () -> tm().transact(() -> tm().load(theEntity.key())));
}
@TestTemplate
@TestOfyAndSql
void maybeLoad_succeeds() {
assertEntityNotExist(theEntity);
tm().transact(() -> tm().insert(theEntity));
@ -227,13 +228,13 @@ public class TransactionManagerTest {
assertThat(persisted.data).isEqualTo("foo");
}
@TestTemplate
@TestOfyAndSql
void maybeLoad_nonExistentObject() {
assertEntityNotExist(theEntity);
assertThat(tm().transact(() -> tm().maybeLoad(theEntity.key())).isPresent()).isFalse();
}
@TestTemplate
@TestOfyAndSql
void delete_succeeds() {
tm().transact(() -> tm().insert(theEntity));
assertEntityExists(theEntity);
@ -242,14 +243,14 @@ public class TransactionManagerTest {
assertEntityNotExist(theEntity);
}
@TestTemplate
@TestOfyAndSql
void delete_doNothingWhenEntityNotExist() {
assertEntityNotExist(theEntity);
tm().transact(() -> tm().delete(theEntity.key()));
assertEntityNotExist(theEntity);
}
@TestTemplate
@TestOfyAndSql
void delete_succeedsForEntitySet() {
assertAllEntitiesNotExist(moreEntities);
tm().transact(() -> tm().insertAll(moreEntities));
@ -261,7 +262,7 @@ public class TransactionManagerTest {
assertAllEntitiesNotExist(moreEntities);
}
@TestTemplate
@TestOfyAndSql
void delete_ignoreNonExistentEntity() {
assertAllEntitiesNotExist(moreEntities);
tm().transact(() -> tm().insertAll(moreEntities));
@ -276,7 +277,16 @@ public class TransactionManagerTest {
assertAllEntitiesNotExist(moreEntities);
}
@TestTemplate
@TestOfyAndSql
void delete_deletesTheGivenEntity() {
tm().transact(() -> tm().insert(theEntity));
assertEntityExists(theEntity);
fakeClock.advanceOneMilli();
tm().transact(() -> tm().delete(theEntity));
assertEntityNotExist(theEntity);
}
@TestOfyAndSql
void load_multi() {
assertAllEntitiesNotExist(moreEntities);
tm().transact(() -> tm().insertAll(moreEntities));
@ -286,7 +296,7 @@ public class TransactionManagerTest {
.isEqualTo(Maps.uniqueIndex(moreEntities, TestEntity::key));
}
@TestTemplate
@TestOfyAndSql
void load_multiWithDuplicateKeys() {
assertAllEntitiesNotExist(moreEntities);
tm().transact(() -> tm().insertAll(moreEntities));
@ -298,7 +308,7 @@ public class TransactionManagerTest {
.isEqualTo(Maps.uniqueIndex(moreEntities, TestEntity::key));
}
@TestTemplate
@TestOfyAndSql
void load_multiMissingKeys() {
assertAllEntitiesNotExist(moreEntities);
tm().transact(() -> tm().insertAll(moreEntities));
@ -310,6 +320,14 @@ public class TransactionManagerTest {
.isEqualTo(Maps.uniqueIndex(moreEntities, TestEntity::key));
}
@TestOfyOnly
void loadAllForOfyTm_throwsExceptionInTransaction() {
assertAllEntitiesNotExist(moreEntities);
tm().transact(() -> tm().insertAll(moreEntities));
assertThrows(
IllegalArgumentException.class, () -> tm().transact(() -> tm().loadAll(TestEntity.class)));
}
private static void assertEntityExists(TestEntity entity) {
assertThat(tm().transact(() -> tm().exists(entity))).isTrue();
}

View file

@ -61,7 +61,7 @@ public enum Fixture {
createTlds("xn--q9jyb4c", "example");
// Used for OT&E TLDs
persistPremiumList("default_sandbox_list");
persistPremiumList("default_sandbox_list", "sandbox,USD 1000");
try {
OteStatsTestHelper.setupCompleteOte("otefinished");

View file

@ -49,7 +49,7 @@ abstract class AbstractEppResourceSubject<
this.actual = subject;
}
private List<HistoryEntry> getHistoryEntries() {
private List<? extends HistoryEntry> getHistoryEntries() {
return DatastoreHelper.getHistoryEntries(actual);
}
@ -120,7 +120,7 @@ abstract class AbstractEppResourceSubject<
// TODO(weiminyu): Remove after next Truth update
@SuppressWarnings("UnnecessaryParentheses")
public Which<HistoryEntrySubject> hasHistoryEntryAtIndex(int index) {
List<HistoryEntry> historyEntries = getHistoryEntries();
List<? extends HistoryEntry> historyEntries = getHistoryEntries();
check("getHistoryEntries().size()").that(historyEntries.size()).isAtLeast(index + 1);
return new Which<>(
check("getHistoryEntries(%s)", index)

View file

@ -17,6 +17,7 @@ package google.registry.testing;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.io.Files.asCharSink;
import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.persistence.transaction.TransactionManagerUtil.ofyOrJpaTm;
import static google.registry.testing.DatastoreHelper.persistSimpleResources;
import static google.registry.testing.DualDatabaseTestInvocationContextProvider.injectTmForDualDatabaseTest;
import static google.registry.testing.DualDatabaseTestInvocationContextProvider.restoreTmAfterDualDatabaseTest;
@ -384,6 +385,17 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
if (isWithDatastoreAndCloudSql()) {
injectTmForDualDatabaseTest(context);
}
ofyOrJpaTm(
() -> {
if (withDatastore && !withoutCannedData) {
loadInitialData();
}
},
() -> {
if (withCloudSql && !withJpaUnitTest && !withoutCannedData) {
loadInitialData();
}
});
}
/**
@ -458,9 +470,6 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
ObjectifyService.initOfy();
// Reset id allocation in ObjectifyService so that ids are deterministic in tests.
ObjectifyService.resetNextTestId();
if (!withoutCannedData) {
loadInitialData();
}
this.ofyTestEntities.forEach(AppEngineExtension::register);
}
}

View file

@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Suppliers.memoize;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.Iterables.toArray;
import static com.google.common.collect.MoreCollectors.onlyElement;
import static com.google.common.truth.Truth.assertThat;
@ -33,6 +34,9 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.registry.Registry.TldState.GENERAL_AVAILABILITY;
import static google.registry.model.registry.label.PremiumListUtils.parentPremiumListEntriesOnRevision;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.ofyOrJpaTm;
import static google.registry.persistence.transaction.TransactionManagerUtil.ofyTmOrDoNothing;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
import static google.registry.util.CollectionUtils.difference;
import static google.registry.util.CollectionUtils.union;
@ -44,6 +48,7 @@ import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import static java.util.Arrays.asList;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.fail;
import com.google.common.base.Ascii;
import com.google.common.base.Splitter;
@ -60,22 +65,27 @@ import google.registry.dns.writer.VoidDnsWriter;
import google.registry.model.Buildable;
import google.registry.model.EppResource;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactBase;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainContent;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid;
import google.registry.model.host.HostBase;
import google.registry.model.host.HostHistory;
import google.registry.model.host.HostResource;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.EppResourceIndexBucket;
@ -101,10 +111,14 @@ import google.registry.persistence.VKey;
import google.registry.tmch.LordnTaskUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.DateTimeComparator;
import org.joda.time.DateTimeZone;
/** Static utils for setting up test resources. */
public class DatastoreHelper {
@ -325,18 +339,40 @@ public class DatastoreHelper {
* the requirement to have monotonically increasing timestamps.
*/
public static PremiumList persistPremiumList(String listName, String... lines) {
PremiumList premiumList = new PremiumList.Builder().setName(listName).build();
ImmutableMap<String, PremiumListEntry> entries = premiumList.parse(asList(lines));
checkState(lines.length != 0, "Must provide at least one premium entry");
PremiumList partialPremiumList = new PremiumList.Builder().setName(listName).build();
ImmutableMap<String, PremiumListEntry> entries = partialPremiumList.parse(asList(lines));
CurrencyUnit currencyUnit =
entries.entrySet().iterator().next().getValue().getValue().getCurrencyUnit();
PremiumList premiumList =
partialPremiumList
.asBuilder()
.setCreationTime(DateTime.now(DateTimeZone.UTC))
.setCurrency(currencyUnit)
.setLabelsToPrices(
entries.entrySet().stream()
.collect(
toImmutableMap(
Map.Entry::getKey, entry -> entry.getValue().getValue().getAmount())))
.build();
PremiumListRevision revision = PremiumListRevision.create(premiumList, entries.keySet());
ofy()
.saveWithoutBackup()
.entities(premiumList.asBuilder().setRevision(Key.create(revision)).build(), revision)
.now();
ofy()
.saveWithoutBackup()
.entities(parentPremiumListEntriesOnRevision(entries.values(), Key.create(revision)))
.now();
return ofy().load().entity(premiumList).now();
ofyOrJpaTm(
() -> {
tm().putAllWithoutBackup(
ImmutableList.of(
premiumList.asBuilder().setRevision(Key.create(revision)).build(), revision));
tm().putAllWithoutBackup(
parentPremiumListEntriesOnRevision(entries.values(), Key.create(revision)));
},
() -> tm().transact(() -> tm().insert(premiumList)));
// The above premiumList is in the session cache and it is different from the corresponding
// entity stored in Datastore because it has some @Ignore fields set dedicated for SQL. This
// breaks the assumption we have in our application code, see
// PremiumListUtils.savePremiumListAndEntries(). Clearing the session cache can help make sure
// we always get the same list.
tm().clearSessionCache();
return transactIfJpaTm(() -> tm().load(premiumList));
}
/** Creates and persists a tld. */
@ -672,17 +708,28 @@ public class DatastoreHelper {
}
private static Iterable<BillingEvent> getBillingEvents() {
return Iterables.concat(
ofy().load().type(BillingEvent.OneTime.class),
ofy().load().type(BillingEvent.Recurring.class),
ofy().load().type(BillingEvent.Cancellation.class));
return transactIfJpaTm(
() ->
Iterables.concat(
tm().loadAll(BillingEvent.OneTime.class),
tm().loadAll(BillingEvent.Recurring.class),
tm().loadAll(BillingEvent.Cancellation.class)));
}
private static Iterable<BillingEvent> getBillingEvents(EppResource resource) {
return Iterables.concat(
ofy().load().type(BillingEvent.OneTime.class).ancestor(resource),
ofy().load().type(BillingEvent.Recurring.class).ancestor(resource),
ofy().load().type(BillingEvent.Cancellation.class).ancestor(resource));
return transactIfJpaTm(
() ->
Iterables.concat(
tm().loadAll(BillingEvent.OneTime.class).stream()
.filter(oneTime -> oneTime.getDomainRepoId().equals(resource.getRepoId()))
.collect(toImmutableList()),
tm().loadAll(BillingEvent.Recurring.class).stream()
.filter(recurring -> recurring.getDomainRepoId().equals(resource.getRepoId()))
.collect(toImmutableList()),
tm().loadAll(BillingEvent.Cancellation.class).stream()
.filter(
cancellation -> cancellation.getDomainRepoId().equals(resource.getRepoId()))
.collect(toImmutableList())));
}
/** Assert that the actual billing event matches the expected one, ignoring IDs. */
@ -751,41 +798,63 @@ public class DatastoreHelper {
assertPollMessagesEqual(getPollMessages(), Arrays.asList(expected));
}
public static void assertPollMessagesForResource(EppResource resource, PollMessage... expected) {
assertPollMessagesEqual(getPollMessages(resource), Arrays.asList(expected));
public static void assertPollMessagesForResource(DomainContent domain, PollMessage... expected) {
assertPollMessagesEqual(getPollMessages(domain), Arrays.asList(expected));
}
public static ImmutableList<PollMessage> getPollMessages() {
return ImmutableList.copyOf(ofy().load().type(PollMessage.class));
return ImmutableList.copyOf(transactIfJpaTm(() -> tm().loadAll(PollMessage.class)));
}
public static ImmutableList<PollMessage> getPollMessages(String clientId) {
return ImmutableList.copyOf(ofy().load().type(PollMessage.class).filter("clientId", clientId));
return transactIfJpaTm(
() ->
tm().loadAll(PollMessage.class).stream()
.filter(pollMessage -> pollMessage.getClientId().equals(clientId))
.collect(toImmutableList()));
}
public static ImmutableList<PollMessage> getPollMessages(EppResource resource) {
return ImmutableList.copyOf(ofy().load().type(PollMessage.class).ancestor(resource));
public static ImmutableList<PollMessage> getPollMessages(DomainContent domain) {
return transactIfJpaTm(
() ->
tm().loadAll(PollMessage.class).stream()
.filter(
pollMessage ->
pollMessage.getParentKey().getParent().getName().equals(domain.getRepoId()))
.collect(toImmutableList()));
}
public static ImmutableList<PollMessage> getPollMessages(String clientId, DateTime now) {
return ImmutableList.copyOf(
ofy()
.load()
.type(PollMessage.class)
.filter("clientId", clientId)
.filter("eventTime <=", now.toDate()));
return transactIfJpaTm(
() ->
tm().loadAll(PollMessage.class).stream()
.filter(pollMessage -> pollMessage.getClientId().equals(clientId))
.filter(
pollMessage ->
pollMessage.getEventTime().isEqual(now)
|| pollMessage.getEventTime().isBefore(now))
.collect(toImmutableList()));
}
/** Gets all PollMessages associated with the given EppResource. */
public static ImmutableList<PollMessage> getPollMessages(
EppResource resource, String clientId, DateTime now) {
return ImmutableList.copyOf(
ofy()
.load()
.type(PollMessage.class)
.ancestor(resource)
.filter("clientId", clientId)
.filter("eventTime <=", now.toDate()));
return transactIfJpaTm(
() ->
tm().loadAll(PollMessage.class).stream()
.filter(
pollMessage ->
pollMessage
.getParentKey()
.getParent()
.getName()
.equals(resource.getRepoId()))
.filter(pollMessage -> pollMessage.getClientId().equals(clientId))
.filter(
pollMessage ->
pollMessage.getEventTime().isEqual(now)
|| pollMessage.getEventTime().isBefore(now))
.collect(toImmutableList()));
}
public static PollMessage getOnlyPollMessage(String clientId) {
@ -808,19 +877,15 @@ public class DatastoreHelper {
}
public static PollMessage getOnlyPollMessage(
EppResource resource,
String clientId,
DateTime now,
Class<? extends PollMessage> subType) {
return getPollMessages(resource, clientId, now)
.stream()
DomainContent domain, String clientId, DateTime now, Class<? extends PollMessage> subType) {
return getPollMessages(domain, clientId, now).stream()
.filter(subType::isInstance)
.map(subType::cast)
.collect(onlyElement());
}
public static void assertAllocationTokens(AllocationToken... expectedTokens) {
assertThat(ofy().load().type(AllocationToken.class).list())
assertThat(transactIfJpaTm(() -> tm().loadAll(AllocationToken.class)))
.comparingElementsUsing(immutableObjectCorrespondence("updateTimestamp", "creationTime"))
.containsExactlyElementsIn(expectedTokens);
}
@ -861,13 +926,17 @@ public class DatastoreHelper {
}
private static <R> void saveResource(R resource, boolean wantBackup) {
Saver saver = wantBackup ? ofy().save() : ofy().saveWithoutBackup();
saver.entity(resource);
if (resource instanceof EppResource) {
EppResource eppResource = (EppResource) resource;
persistEppResourceExtras(
eppResource, EppResourceIndex.create(Key.create(eppResource)), saver);
}
ofyOrJpaTm(
() -> {
Saver saver = wantBackup ? ofy().save() : ofy().saveWithoutBackup();
saver.entity(resource);
if (resource instanceof EppResource) {
EppResource eppResource = (EppResource) resource;
persistEppResourceExtras(
eppResource, EppResourceIndex.create(Key.create(eppResource)), saver);
}
},
() -> tm().put(resource));
}
private static <R extends EppResource> void persistEppResourceExtras(
@ -890,23 +959,25 @@ public class DatastoreHelper {
// Datastore and not from the session cache. This is needed to trigger Objectify's load process
// (unmarshalling entity protos to POJOs, nulling out empty collections, calling @OnLoad
// methods, etc.) which is bypassed for entities loaded from the session cache.
ofy().clearSessionCache();
return ofy().load().entity(resource).now();
tm().clearSessionCache();
return transactIfJpaTm(() -> tm().load(resource));
}
/** Persists an EPP resource with the {@link EppResourceIndex} always going into bucket one. */
public static <R extends EppResource> R persistEppResourceInFirstBucket(final R resource) {
final EppResourceIndex eppResourceIndex =
EppResourceIndex.create(Key.create(EppResourceIndexBucket.class, 1), Key.create(resource));
tm()
.transact(
() -> {
Saver saver = ofy().save();
saver.entity(resource);
persistEppResourceExtras(resource, eppResourceIndex, saver);
});
ofy().clearSessionCache();
return ofy().load().entity(resource).now();
tm().transact(
() ->
ofyOrJpaTm(
() -> {
Saver saver = ofy().save();
saver.entity(resource);
persistEppResourceExtras(resource, eppResourceIndex, saver);
},
() -> tm().put(resource)));
tm().clearSessionCache();
return transactIfJpaTm(() -> tm().load(resource));
}
public static <R> void persistResources(final Iterable<R> resources) {
@ -925,9 +996,9 @@ public class DatastoreHelper {
}
// Force the session to be cleared so that when we read it back, we read from Datastore
// and not from the transaction's session cache.
ofy().clearSessionCache();
tm().clearSessionCache();
for (R resource : resources) {
ofy().load().entity(resource).now();
transactIfJpaTm(() -> tm().load(resource));
}
}
@ -943,31 +1014,57 @@ public class DatastoreHelper {
*/
public static <R extends EppResource> R persistEppResource(final R resource) {
checkState(!tm().inTransaction());
tm()
.transact(
tm().transact(
() -> {
ofy()
.save()
.<ImmutableObject>entities(
resource,
tm().put(resource);
tm().put(
new HistoryEntry.Builder()
.setParent(resource)
.setType(getHistoryEntryType(resource))
.setModificationTime(tm().getTransactionTime())
.build());
ofy().save().entity(ForeignKeyIndex.create(resource, resource.getDeletionTime()));
.build()
.toChildHistoryEntity());
ofyTmOrDoNothing(
() -> tm().put(ForeignKeyIndex.create(resource, resource.getDeletionTime())));
});
ofy().clearSessionCache();
return ofy().load().entity(resource).safe();
tm().clearSessionCache();
return transactIfJpaTm(() -> tm().load(resource));
}
/** Returns all of the history entries that are parented off the given EppResource. */
public static List<HistoryEntry> getHistoryEntries(EppResource resource) {
return ofy().load()
.type(HistoryEntry.class)
.ancestor(resource)
.order("modificationTime")
.list();
public static List<? extends HistoryEntry> getHistoryEntries(EppResource resource) {
return ofyOrJpaTm(
() ->
ofy()
.load()
.type(HistoryEntry.class)
.ancestor(resource)
.order("modificationTime")
.list(),
() ->
tm().transact(
() -> {
ImmutableList<? extends HistoryEntry> unsorted = null;
if (resource instanceof ContactBase) {
unsorted = tm().loadAll(ContactHistory.class);
} else if (resource instanceof HostBase) {
unsorted = tm().loadAll(HostHistory.class);
} else if (resource instanceof DomainContent) {
unsorted = tm().loadAll(DomainHistory.class);
} else {
fail("Expected an EppResource instance, but got " + resource.getClass());
}
ImmutableList<? extends HistoryEntry> filtered =
unsorted.stream()
.filter(
historyEntry ->
historyEntry
.getParent()
.getName()
.equals(resource.getRepoId()))
.collect(toImmutableList());
return ImmutableList.sortedCopyOf(DateTimeComparator.getInstance(), filtered);
}));
}
/**
@ -1009,9 +1106,13 @@ public class DatastoreHelper {
}
public static PollMessage getOnlyPollMessageForHistoryEntry(HistoryEntry historyEntry) {
return Iterables.getOnlyElement(ofy().load()
.type(PollMessage.class)
.ancestor(historyEntry));
return Iterables.getOnlyElement(
transactIfJpaTm(
() ->
tm().loadAll(PollMessage.class).stream()
.filter(
pollMessage -> pollMessage.getParentKey().equals(Key.create(historyEntry)))
.collect(toImmutableList())));
}
public static <T extends EppResource> HistoryEntry createHistoryEntryForEppResource(
@ -1029,23 +1130,30 @@ public class DatastoreHelper {
* ForeignKeyedEppResources.
*/
public static <R> ImmutableList<R> persistSimpleResources(final Iterable<R> resources) {
tm().transact(() -> ofy().saveWithoutBackup().entities(resources));
tm().transact(() -> tm().putAllWithoutBackup(ImmutableList.copyOf(resources)));
// Force the session to be cleared so that when we read it back, we read from Datastore
// and not from the transaction's session cache.
ofy().clearSessionCache();
return ImmutableList.copyOf(ofy().load().entities(resources).values());
tm().clearSessionCache();
return transactIfJpaTm(() -> tm().loadAll(resources));
}
public static void deleteResource(final Object resource) {
ofy().deleteWithoutBackup().entity(resource).now();
transactIfJpaTm(() -> tm().deleteWithoutBackup(resource));
// Force the session to be cleared so that when we read it back, we read from Datastore and
// not from the transaction's session cache.
ofy().clearSessionCache();
tm().clearSessionCache();
}
/** Force the create and update timestamps to get written into the resource. **/
public static <R> R cloneAndSetAutoTimestamps(final R resource) {
return tm().transact(() -> ofy().load().fromEntity(ofy().save().toEntity(resource)));
return tm().transact(
() ->
ofyOrJpaTm(
() -> ofy().load().fromEntity(ofy().save().toEntity(resource)),
() -> {
tm().put(resource);
return tm().load(resource);
}));
}
/** Returns the entire map of {@link PremiumListEntry}s for the given {@link PremiumList}. */

View file

@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList;
import google.registry.persistence.transaction.TransactionManager;
import google.registry.persistence.transaction.TransactionManagerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;
@ -52,9 +53,21 @@ class DualDatabaseTestInvocationContextProvider implements TestTemplateInvocatio
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
ExtensionContext context) {
return Stream.of(
createInvocationContext("Test Datastore", TransactionManagerFactory::ofyTm),
createInvocationContext("Test PostgreSQL", TransactionManagerFactory::jpaTm));
TestTemplateInvocationContext ofyContext =
createInvocationContext("Test Datastore", TransactionManagerFactory::ofyTm);
TestTemplateInvocationContext sqlContext =
createInvocationContext("Test PostgreSQL", TransactionManagerFactory::jpaTm);
Method testMethod = context.getTestMethod().orElseThrow(IllegalStateException::new);
if (testMethod.isAnnotationPresent(TestOfyAndSql.class)) {
return Stream.of(ofyContext, sqlContext);
} else if (testMethod.isAnnotationPresent(TestOfyOnly.class)) {
return Stream.of(ofyContext);
} else if (testMethod.isAnnotationPresent(TestSqlOnly.class)) {
return Stream.of(sqlContext);
} else {
throw new IllegalStateException(
"Test method must be annotated with @TestOfyAndSql, @TestOfyOnly or @TestSqlOnly");
}
}
private TestTemplateInvocationContext createInvocationContext(
@ -104,6 +117,18 @@ class DualDatabaseTestInvocationContextProvider implements TestTemplateInvocatio
static void injectTmForDualDatabaseTest(ExtensionContext context) {
if (isDualDatabaseTest(context)) {
context
.getTestMethod()
.ifPresent(
testMethod -> {
if (!testMethod.isAnnotationPresent(TestOfyAndSql.class)
&& !testMethod.isAnnotationPresent(TestOfyOnly.class)
&& !testMethod.isAnnotationPresent(TestSqlOnly.class)) {
throw new IllegalStateException(
"Test method must be annotated with @TestOfyAndSql, @TestOfyOnly or"
+ " @TestSqlOnly");
}
});
context.getStore(NAMESPACE).put(ORIGINAL_TM_KEY, tm());
Supplier<? extends TransactionManager> tmSupplier =
(Supplier<? extends TransactionManager>)

View file

@ -19,37 +19,67 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import google.registry.model.ofy.DatastoreTransactionManager;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.persistence.transaction.TransactionManager;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.RegisterExtension;
/**
* Test to verify that {@link DualDatabaseTestInvocationContextProvider} extension executes {@link
* TestTemplate} test twice with different databases.
* Test to verify that {@link DualDatabaseTestInvocationContextProvider} extension executes tests
* with corresponding {@link TransactionManager}.
*/
@DualDatabaseTest
public class DualDatabaseTestInvocationContextProviderTest {
private static int datastoreTestCounter = 0;
private static int postgresqlTestCounter = 0;
private static int testBothDbsOfyCounter = 0;
private static int testBothDbsSqlCounter = 0;
private static int testOfyOnlyOfyCounter = 0;
private static int testOfyOnlySqlCounter = 0;
private static int testSqlOnlyOfyCounter = 0;
private static int testSqlOnlySqlCounter = 0;
@RegisterExtension
public final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
@TestTemplate
void testToUseTransactionManager() {
@TestOfyAndSql
void testToVerifyBothOfyAndSqlTmAreUsed() {
if (tm() instanceof DatastoreTransactionManager) {
datastoreTestCounter++;
testBothDbsOfyCounter++;
}
if (tm() instanceof JpaTransactionManager) {
postgresqlTestCounter++;
testBothDbsSqlCounter++;
}
}
@TestOfyOnly
void testToVerifyOnlyOfyTmIsUsed() {
if (tm() instanceof DatastoreTransactionManager) {
testOfyOnlyOfyCounter++;
}
if (tm() instanceof JpaTransactionManager) {
testOfyOnlySqlCounter++;
}
}
@TestSqlOnly
void testToVerifyOnlySqlTmIsUsed() {
if (tm() instanceof DatastoreTransactionManager) {
testSqlOnlyOfyCounter++;
}
if (tm() instanceof JpaTransactionManager) {
testSqlOnlySqlCounter++;
}
}
@AfterAll
static void assertEachTransactionManagerIsUsed() {
assertThat(datastoreTestCounter).isEqualTo(1);
assertThat(postgresqlTestCounter).isEqualTo(1);
assertThat(testBothDbsOfyCounter).isEqualTo(1);
assertThat(testBothDbsSqlCounter).isEqualTo(1);
assertThat(testOfyOnlyOfyCounter).isEqualTo(1);
assertThat(testOfyOnlySqlCounter).isEqualTo(0);
assertThat(testSqlOnlyOfyCounter).isEqualTo(0);
assertThat(testSqlOnlySqlCounter).isEqualTo(1);
}
}

View file

@ -0,0 +1,31 @@
// 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.testing;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.junit.jupiter.api.TestTemplate;
/**
* Annotation to indicate a test method will be executed twice with Datastore and Postgresql
* respectively.
*/
@Target({METHOD})
@Retention(RUNTIME)
@TestTemplate
public @interface TestOfyAndSql {}

View file

@ -0,0 +1,28 @@
// 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.testing;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.junit.jupiter.api.TestTemplate;
/** Annotation to indicate a test method will be executed only with Datastore. */
@Target({METHOD})
@Retention(RUNTIME)
@TestTemplate
public @interface TestOfyOnly {}

View file

@ -0,0 +1,28 @@
// 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.testing;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.junit.jupiter.api.TestTemplate;
/** Annotation to indicate a test method will be executed only with Postgresql. */
@Target({METHOD})
@Retention(RUNTIME)
@TestTemplate
public @interface TestSqlOnly {}