Refactor PremiumList storage and retrieval for dual-database setup (#950)

* Refactor PremiumList storage and retrieval for dual-database setup

Previously, the storage and retrieval code was scattered across various
places haphazardly and there was no good way to set up dual database
access. This reorganizes the code so that retrieval is simpler and it
allows for dual-write and dual-read.

This includes the following changes:

- Move all static / object retrieval code out of PremiumList -- the
class should solely consist of its data and methods on its data and it
shouldn't have to worry about complicated caching or retrieval

- Split all PremiumList retrieval methods into PremiumListDatastoreDao
and PremiumListSqlDao that handle retrieval of the premium list entry
objects from the corresponding databases (since the way the actual data
itself is stored is not the same between the two

- Create a dual-DAO for PremiumList retrieval that branches between
SQL/Datastore depending on which is appropriate -- it will read from
and write to both but only log errors for the secondary DB

- Cache the mapping from name to premium list in the dual-DAO. This is a
common code path regardless of database so we can cache it at a high
level

- Cache the ways to go from premium list -> premium entries in the
Datastore and SQL DAOs. These caches are specific to the corresponding
DB and should thus be stored in the corresponding DAO.

- Moves the database-choosing code from the actions to the lower-level
dual-DAO. This is because we will often wish to access this premium list
data in flows and all accesses should use the proper DB-selecting code
This commit is contained in:
gbrodman 2021-02-22 21:19:48 -05:00 committed by GitHub
parent ffe3124ee1
commit a07fbb27c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1270 additions and 1046 deletions

View file

@ -53,6 +53,7 @@ import google.registry.model.ofy.CommitLogBucket;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.CommitLogMutation;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.ReservedList;
import google.registry.model.server.Lock;
import google.registry.model.tmch.ClaimsListShard;
@ -93,13 +94,14 @@ public class ReplayCommitLogsToSqlActionTest {
.withClock(fakeClock)
.withOfyTestEntities(TestObject.class)
.withJpaUnitTestEntities(
RegistrarContact.class,
TestObject.class,
SqlReplayCheckpoint.class,
ContactResource.class,
DelegationSignerData.class,
DomainBase.class,
GracePeriod.class,
DelegationSignerData.class)
PremiumList.class,
RegistrarContact.class,
SqlReplayCheckpoint.class,
TestObject.class)
.build();
/** Local GCS service. */

View file

@ -34,6 +34,9 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.ofy.Ofy;
import google.registry.model.registry.Registry;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import google.registry.tools.LevelDbLogReader;
@ -48,6 +51,7 @@ import org.apache.beam.sdk.values.KV;
import org.joda.time.DateTime;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
@ -58,6 +62,15 @@ public class BackupTestStoreTest {
@TempDir File tempDir;
@RegisterExtension
final transient JpaIntegrationTestExtension jpaIntegrationTestExtension =
new JpaTestRules.Builder().buildIntegrationTestRule();
@RegisterExtension
@Order(value = 1)
final transient DatastoreEntityExtension datastoreEntityExtension =
new DatastoreEntityExtension();
@RegisterExtension InjectExtension injectRule = new InjectExtension();
private FakeClock fakeClock;

View file

@ -26,6 +26,9 @@ import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.ofy.Ofy;
import google.registry.model.registry.Registry;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import java.io.File;
@ -44,6 +47,7 @@ import org.apache.beam.sdk.values.PCollection;
import org.joda.time.DateTime;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
@ -52,6 +56,7 @@ import org.junit.jupiter.api.io.TempDir;
class CommitLogTransformsTest implements Serializable {
private static final DateTime START_TIME = DateTime.parse("2000-01-01T00:00:00.0Z");
private final FakeClock fakeClock = new FakeClock(START_TIME);
@SuppressWarnings("WeakerAccess")
@TempDir
@ -59,11 +64,19 @@ class CommitLogTransformsTest implements Serializable {
@RegisterExtension final transient InjectExtension injectRule = new InjectExtension();
@RegisterExtension
final transient JpaIntegrationTestExtension jpaIntegrationTestExtension =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationTestRule();
@RegisterExtension
@Order(value = 1)
final transient DatastoreEntityExtension datastoreEntityExtension =
new DatastoreEntityExtension();
@RegisterExtension
final transient TestPipelineExtension testPipeline =
TestPipelineExtension.create().enableAbandonedNodeEnforcement(true);
private FakeClock fakeClock;
private transient BackupTestStore store;
private File commitLogsDir;
private File firstCommitLogFile;
@ -75,7 +88,6 @@ class CommitLogTransformsTest implements Serializable {
@BeforeEach
void beforeEach() throws Exception {
fakeClock = new FakeClock(START_TIME);
store = new BackupTestStore(fakeClock);
injectRule.setStaticField(Ofy.class, "clock", fakeClock);

View file

@ -68,7 +68,7 @@ public class DomainBaseUtilTest {
@RegisterExtension
AppEngineExtension appEngineRule =
AppEngineExtension.builder().withDatastore().withClock(fakeClock).build();
AppEngineExtension.builder().withDatastoreAndCloudSql().withClock(fakeClock).build();
@RegisterExtension InjectExtension injectRule = new InjectExtension();

View file

@ -27,6 +27,9 @@ import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.ofy.Ofy;
import google.registry.model.registry.Registry;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import java.io.File;
@ -44,6 +47,7 @@ import org.apache.beam.sdk.values.PCollection;
import org.joda.time.DateTime;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
@ -53,7 +57,7 @@ import org.junit.jupiter.api.io.TempDir;
*
* <p>This class implements {@link Serializable} so that test {@link DoFn} classes may be inlined.
*/
class ExportloadingTransformsTest implements Serializable {
class ExportLoadingTransformsTest implements Serializable {
private static final DateTime START_TIME = DateTime.parse("2000-01-01T00:00:00.0Z");
@ -68,6 +72,15 @@ class ExportloadingTransformsTest implements Serializable {
@RegisterExtension final transient InjectExtension injectRule = new InjectExtension();
@RegisterExtension
final transient JpaIntegrationTestExtension jpaIntegrationTestExtension =
new JpaTestRules.Builder().buildIntegrationTestRule();
@RegisterExtension
@Order(value = 1)
final transient DatastoreEntityExtension datastoreEntityExtension =
new DatastoreEntityExtension();
@RegisterExtension
final transient TestPipelineExtension testPipeline =
TestPipelineExtension.create().enableAbandonedNodeEnforcement(true);

View file

@ -29,6 +29,9 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.ofy.Ofy;
import google.registry.model.registry.Registry;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import java.io.File;
@ -38,6 +41,7 @@ import org.apache.beam.sdk.values.KV;
import org.apache.beam.sdk.values.PCollectionTuple;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
@ -84,6 +88,15 @@ class LoadDatastoreSnapshotTest {
@RegisterExtension final transient InjectExtension injectRule = new InjectExtension();
@RegisterExtension
final transient JpaIntegrationTestExtension jpaIntegrationTestExtension =
new JpaTestRules.Builder().buildIntegrationTestRule();
@RegisterExtension
@Order(value = 1)
final transient DatastoreEntityExtension datastoreEntityExtension =
new DatastoreEntityExtension();
@RegisterExtension
final transient TestPipelineExtension testPipeline =
TestPipelineExtension.create().enableAbandonedNodeEnforcement(true);

View file

@ -17,8 +17,6 @@ package google.registry.export;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static google.registry.export.ExportPremiumTermsAction.EXPORT_MIME_TYPE;
import static google.registry.export.ExportPremiumTermsAction.PREMIUM_TERMS_FILENAME;
import static google.registry.model.registry.label.PremiumListUtils.deletePremiumList;
import static google.registry.model.registry.label.PremiumListUtils.savePremiumListAndEntries;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.deleteTld;
import static google.registry.testing.DatabaseHelper.persistResource;
@ -39,6 +37,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.net.MediaType;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.request.Response;
import google.registry.storage.drive.DriveConnection;
import google.registry.testing.AppEngineExtension;
@ -76,8 +75,7 @@ public class ExportPremiumTermsActionTest {
@BeforeEach
void beforeEach() throws Exception {
createTld("tld");
PremiumList pl = new PremiumList.Builder().setName("pl-name").build();
savePremiumListAndEntries(pl, PREMIUM_NAMES);
PremiumList pl = PremiumListDualDao.save("pl-name", PREMIUM_NAMES);
persistResource(
Registry.get("tld").asBuilder().setPremiumList(pl).setDriveFolderId("folder_id").build());
when(driveConnection.createOrUpdateFile(
@ -106,26 +104,6 @@ public class ExportPremiumTermsActionTest {
verifyNoMoreInteractions(response);
}
@Test
void test_exportPremiumTerms_success_emptyPremiumList() throws IOException {
PremiumList pl = new PremiumList.Builder().setName("pl-name").build();
savePremiumListAndEntries(pl, ImmutableList.of());
runAction("tld");
verify(driveConnection)
.createOrUpdateFile(
PREMIUM_TERMS_FILENAME,
EXPORT_MIME_TYPE,
"folder_id",
DISCLAIMER_WITH_NEWLINE.getBytes(UTF_8));
verifyNoMoreInteractions(driveConnection);
verify(response).setStatus(SC_OK);
verify(response).setPayload("file_id");
verify(response).setContentType(PLAIN_TEXT_UTF_8);
verifyNoMoreInteractions(response);
}
@Test
void test_exportPremiumTerms_doNothing_listNotConfigured() {
persistResource(Registry.get("tld").asBuilder().setPremiumList(null).build());
@ -165,7 +143,7 @@ public class ExportPremiumTermsActionTest {
@Test
void test_exportPremiumTerms_failure_noPremiumList() {
deletePremiumList(new PremiumList.Builder().setName("pl-name").build());
PremiumListDualDao.delete(new PremiumList.Builder().setName("pl-name").build());
assertThrows(RuntimeException.class, () -> runAction("tld"));
verifyNoInteractions(driveConnection);

View file

@ -41,6 +41,7 @@ import google.registry.model.EntityTestCase;
import google.registry.model.registry.Registry.RegistryNotFoundException;
import google.registry.model.registry.Registry.TldState;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.model.registry.label.ReservedList;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
@ -204,7 +205,7 @@ public class RegistryTest extends EntityTestCase {
Registry registry = Registry.get("tld").asBuilder().setPremiumList(pl2).build();
Key<PremiumList> plKey = registry.getPremiumList();
assertThat(plKey).isNotNull();
PremiumList stored = PremiumList.getUncached(plKey.getName()).get();
PremiumList stored = PremiumListDualDao.getLatestRevision(plKey.getName()).get();
assertThat(stored.getName()).isEqualTo("tld2");
}

View file

@ -14,6 +14,7 @@
package google.registry.model.registry.label;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.monitoring.metrics.contrib.DistributionMetricSubject.assertThat;
@ -26,37 +27,33 @@ import static google.registry.model.registry.label.DomainLabelMetrics.PremiumLis
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.UNCACHED_POSITIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.premiumListChecks;
import static google.registry.model.registry.label.DomainLabelMetrics.premiumListProcessingTime;
import static google.registry.model.registry.label.PremiumListUtils.deletePremiumList;
import static google.registry.model.registry.label.PremiumListUtils.doesPremiumListExist;
import static google.registry.model.registry.label.PremiumListUtils.getPremiumPrice;
import static google.registry.model.registry.label.PremiumListUtils.savePremiumListAndEntries;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadPremiumListEntries;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.joda.time.Duration.standardDays;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.googlecode.objectify.Key;
import google.registry.dns.writer.VoidDnsWriter;
import google.registry.model.pricing.StaticPremiumListPricingEngine;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumList.PremiumListRevision;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.TestCacheExtension;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import org.joda.money.Money;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link PremiumListUtils}. */
public class PremiumListUtilsTest {
/** Unit tests for {@link PremiumListDatastoreDao}. */
public class PremiumListDatastoreDaoTest {
@RegisterExtension
public final AppEngineExtension appEngine =
@ -70,11 +67,13 @@ public class PremiumListUtilsTest {
.withPremiumListEntriesCache(standardDays(1))
.build();
private PremiumList pl;
@BeforeEach
void before() {
// createTld() overwrites the premium list, so call it before persisting pl.
createTld("tld");
PremiumList pl =
pl =
persistPremiumList(
"tld",
"lol,USD 999 # yup",
@ -98,66 +97,39 @@ public class PremiumListUtilsTest {
.hasNoOtherValues();
}
@Test
void testGetPremiumPrice_returnsNoPriceWhenNoPremiumListConfigured() {
createTld("ghost");
persistResource(
new Registry.Builder()
.setTldStr("ghost")
.setPremiumPricingEngine(StaticPremiumListPricingEngine.NAME)
.setDnsWriters(ImmutableSet.of(VoidDnsWriter.NAME))
.build());
assertThat(Registry.get("ghost").getPremiumList()).isNull();
assertThat(getPremiumPrice("blah", Registry.get("ghost"))).isEmpty();
assertThat(premiumListChecks).hasNoOtherValues();
assertThat(premiumListProcessingTime).hasNoOtherValues();
}
@Test
void testGetPremiumPrice_throwsExceptionWhenNonExistentPremiumListConfigured() {
deletePremiumList(PremiumList.getUncached("tld").get());
IllegalStateException thrown =
assertThrows(
IllegalStateException.class, () -> getPremiumPrice("blah", Registry.get("tld")));
assertThat(thrown).hasMessageThat().contains("Could not load premium list 'tld'");
}
@Test
void testSave_largeNumberOfEntries_succeeds() {
PremiumList premiumList = persistHumongousPremiumList("tld", 2500);
assertThat(loadPremiumListEntries(premiumList)).hasSize(2500);
assertThat(getPremiumPrice("7", Registry.get("tld"))).hasValue(Money.parse("USD 100"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "7", "tld"))
.hasValue(Money.parse("USD 100"));
assertMetricOutcomeCount(1, UNCACHED_POSITIVE);
}
@Test
void testSave_updateTime_isUpdatedOnEverySave() {
PremiumList pl =
savePremiumListAndEntries(
new PremiumList.Builder().setName("tld3").build(), ImmutableList.of("slime,USD 10"));
PremiumList newPl =
savePremiumListAndEntries(
new PremiumList.Builder().setName(pl.getName()).build(),
ImmutableList.of("mutants,USD 20"));
PremiumList pl = PremiumListDatastoreDao.save("tld3", ImmutableList.of("slime,USD 10"));
PremiumList newPl = PremiumListDatastoreDao.save("tld3", ImmutableList.of("mutants,USD 20"));
assertThat(newPl.getLastUpdateTime()).isGreaterThan(pl.getLastUpdateTime());
}
@Test
void testSave_creationTime_onlyUpdatedOnFirstCreation() {
PremiumList pl = persistPremiumList("tld3", "sludge,JPY 1000");
PremiumList newPl = savePremiumListAndEntries(pl, ImmutableList.of("sleighbells,CHF 2000"));
PremiumList newPl =
PremiumListDatastoreDao.save("tld3", ImmutableList.of("sleighbells,CHF 2000"));
assertThat(newPl.creationTime).isEqualTo(pl.creationTime);
}
@Test
void testExists() {
assertThat(doesPremiumListExist("tld")).isTrue();
assertThat(doesPremiumListExist("nonExistentPremiumList")).isFalse();
assertThat(PremiumListDatastoreDao.getLatestRevision("tld")).isPresent();
assertThat(PremiumListDatastoreDao.getLatestRevision("nonExistentPremiumList")).isEmpty();
}
@Test
void testGetPremiumPrice_comesFromBloomFilter() throws Exception {
PremiumList pl = PremiumList.getCached("tld").get();
PremiumList pl = PremiumListDatastoreDao.getLatestRevision("tld").get();
PremiumListEntry entry =
persistResource(
new PremiumListEntry.Builder()
@ -167,10 +139,10 @@ public class PremiumListUtilsTest {
.build());
// "missingno" shouldn't be in the Bloom filter, thus it should return not premium without
// attempting to load the entity that is actually present.
assertThat(getPremiumPrice("missingno", Registry.get("tld"))).isEmpty();
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "missingno", "tld")).isEmpty();
// However, if we manually query the cache to force an entity load, it should be found.
assertThat(
PremiumList.cachePremiumListEntries.get(
PremiumListDatastoreDao.premiumListEntriesCache.get(
Key.create(pl.getRevisionKey(), PremiumListEntry.class, "missingno")))
.hasValue(entry);
assertMetricOutcomeCount(1, BLOOM_FILTER_NEGATIVE);
@ -178,8 +150,10 @@ public class PremiumListUtilsTest {
@Test
void testGetPremiumPrice_cachedSecondTime() {
assertThat(getPremiumPrice("rich", Registry.get("tld"))).hasValue(Money.parse("USD 1999"));
assertThat(getPremiumPrice("rich", Registry.get("tld"))).hasValue(Money.parse("USD 1999"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "rich", "tld"))
.hasValue(Money.parse("USD 1999"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "rich", "tld"))
.hasValue(Money.parse("USD 1999"));
assertThat(premiumListChecks)
.hasValueForLabels(1, "tld", "tld", UNCACHED_POSITIVE.toString())
.and()
@ -197,20 +171,15 @@ public class PremiumListUtilsTest {
@Test
void testGetPremiumPrice_bloomFilterFalsePositive() {
// Remove one of the premium list entries from behind the Bloom filter's back.
tm()
.transactNew(
tm().transactNew(
() ->
ofy()
.delete()
.keys(
Key.create(
PremiumList.getCached("tld").get().getRevisionKey(),
PremiumListEntry.class,
"rich")));
.keys(Key.create(pl.getRevisionKey(), PremiumListEntry.class, "rich")));
ofy().clearSessionCache();
assertThat(getPremiumPrice("rich", Registry.get("tld"))).isEmpty();
assertThat(getPremiumPrice("rich", Registry.get("tld"))).isEmpty();
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "rich", "tld")).isEmpty();
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "rich", "tld")).isEmpty();
assertThat(premiumListChecks)
.hasValueForLabels(1, "tld", "tld", UNCACHED_NEGATIVE.toString())
@ -228,22 +197,26 @@ public class PremiumListUtilsTest {
@Test
void testSave_removedPremiumListEntries_areNoLongerInDatastore() {
Registry registry = Registry.get("tld");
PremiumList pl = persistPremiumList("tld", "genius,USD 10", "dolt,JPY 1000");
assertThat(getPremiumPrice("genius", registry)).hasValue(Money.parse("USD 10"));
assertThat(getPremiumPrice("dolt", registry)).hasValue(Money.parse("JPY 1000"));
assertThat(ofy()
.load()
.type(PremiumListEntry.class)
.parent(pl.getRevisionKey())
.id("dolt")
.now()
.price)
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "genius", "tld"))
.hasValue(Money.parse("USD 10"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "dolt", "tld"))
.hasValue(Money.parse("JPY 1000"));
assertThat(
ofy()
.load()
.type(PremiumListEntry.class)
.parent(pl.getRevisionKey())
.id("dolt")
.now()
.price)
.isEqualTo(Money.parse("JPY 1000"));
savePremiumListAndEntries(pl, ImmutableList.of("genius,USD 10", "savant,USD 90"));
assertThat(getPremiumPrice("genius", registry)).hasValue(Money.parse("USD 10"));
assertThat(getPremiumPrice("savant", registry)).hasValue(Money.parse("USD 90"));
assertThat(getPremiumPrice("dolt", registry)).isEmpty();
PremiumListDatastoreDao.save("tld", ImmutableList.of("genius,USD 10", "savant,USD 90"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "genius", "tld"))
.hasValue(Money.parse("USD 10"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "savant", "tld"))
.hasValue(Money.parse("USD 90"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "dolt", "tld")).isEmpty();
// TODO(b/79888775): Assert that the old premium list is enqueued for later deletion.
assertThat(premiumListChecks)
.hasValueForLabels(4, "tld", "tld", UNCACHED_POSITIVE.toString())
@ -261,23 +234,22 @@ public class PremiumListUtilsTest {
@Test
void testGetPremiumPrice_allLabelsAreNonPremium_whenNotInList() {
assertThat(getPremiumPrice("blah", Registry.get("tld"))).isEmpty();
assertThat(getPremiumPrice("slinge", Registry.get("tld"))).isEmpty();
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "blah", "tld")).isEmpty();
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "slinge", "tld")).isEmpty();
assertMetricOutcomeCount(2, BLOOM_FILTER_NEGATIVE);
}
@Test
void testSave_simple() {
PremiumList pl =
savePremiumListAndEntries(
new PremiumList.Builder().setName("tld2").build(),
ImmutableList.of("lol , USD 999 # yupper rooni "));
createTld("tld");
PremiumListDatastoreDao.save("tld2", ImmutableList.of("lol , USD 999 # yupper rooni "));
persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
assertThat(getPremiumPrice("lol", Registry.get("tld"))).hasValue(Money.parse("USD 999"));
assertThat(getPremiumPrice("lol ", Registry.get("tld"))).isEmpty();
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld2", "lol", "tld"))
.hasValue(Money.parse("USD 999"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld2", "lol ", "tld")).isEmpty();
ImmutableMap<String, PremiumListEntry> entries =
loadPremiumListEntries(PremiumList.getUncached("tld2").get());
Streams.stream(PremiumListDatastoreDao.loadPremiumListEntriesUncached(pl))
.collect(toImmutableMap(PremiumListEntry::getLabel, Function.identity()));
assertThat(entries.keySet()).containsExactly("lol");
assertThat(entries).doesNotContainKey("lol ");
PremiumListEntry entry = entries.get("lol");
@ -300,47 +272,47 @@ public class PremiumListUtilsTest {
@Test
void test_saveAndUpdateEntriesTwice() {
PremiumList pl =
savePremiumListAndEntries(
new PremiumList.Builder().setName("pl").build(), ImmutableList.of("test,USD 1"));
ImmutableMap<String, PremiumListEntry> entries = loadPremiumListEntries(pl);
PremiumList firstSave = PremiumListDatastoreDao.save("tld", ImmutableList.of("test,USD 1"));
ImmutableMap<String, PremiumListEntry> entries =
Streams.stream(PremiumListDatastoreDao.loadPremiumListEntriesUncached(firstSave))
.collect(toImmutableMap(PremiumListEntry::getLabel, Function.identity()));
assertThat(entries.keySet()).containsExactly("test");
assertThat(loadPremiumListEntries(PremiumList.getUncached("pl").get())).isEqualTo(entries);
// Save again with no changes, and clear the cache to force a re-load from Datastore.
PremiumList resaved = savePremiumListAndEntries(pl, ImmutableList.of("test,USD 1"));
PremiumList resaved = PremiumListDatastoreDao.save("tld", ImmutableList.of("test,USD 1"));
ofy().clearSessionCache();
Map<String, PremiumListEntry> entriesReloaded =
loadPremiumListEntries(PremiumList.getUncached("pl").get());
Streams.stream(PremiumListDatastoreDao.loadPremiumListEntriesUncached(resaved))
.collect(toImmutableMap(PremiumListEntry::getLabel, Function.identity()));
assertThat(entriesReloaded).hasSize(1);
assertThat(entriesReloaded).containsKey("test");
assertThat(entriesReloaded.get("test").parent).isEqualTo(resaved.getRevisionKey());
}
@Test
void test_savePremiumListAndEntries_clearsCache() {
assertThat(PremiumList.cachePremiumLists.getIfPresent("tld")).isNull();
PremiumList pl = PremiumList.getCached("tld").get();
assertThat(PremiumList.cachePremiumLists.getIfPresent("tld")).isEqualTo(pl);
savePremiumListAndEntries(
new PremiumList.Builder().setName("tld").build(), ImmutableList.of("test,USD 1"));
assertThat(PremiumList.cachePremiumLists.getIfPresent("tld")).isNull();
}
@Test
void testDelete() {
persistPremiumList("gtld1", "trombone,USD 10");
assertThat(PremiumList.getUncached("gtld1")).isPresent();
Key<PremiumListRevision> parent = PremiumList.getUncached("gtld1").get().getRevisionKey();
deletePremiumList(PremiumList.getUncached("gtld1").get());
assertThat(PremiumList.getUncached("gtld1")).isEmpty();
Optional<PremiumList> gtld1 = PremiumListDatastoreDao.getLatestRevision("gtld1");
assertThat(gtld1).isPresent();
Key<PremiumListRevision> parent = gtld1.get().getRevisionKey();
PremiumListDatastoreDao.delete(gtld1.get());
assertThat(PremiumListDatastoreDao.getLatestRevision("gtld1")).isEmpty();
assertThat(ofy().load().type(PremiumListEntry.class).ancestor(parent).list()).isEmpty();
}
@Test
void testDelete_largeNumberOfEntries_succeeds() {
persistHumongousPremiumList("ginormous", 2500);
deletePremiumList(PremiumList.getUncached("ginormous").get());
assertThat(PremiumList.getUncached("ginormous")).isEmpty();
PremiumList large = persistHumongousPremiumList("ginormous", 2500);
PremiumListDatastoreDao.delete(large);
assertThat(PremiumListDatastoreDao.getLatestRevision("ginormous")).isEmpty();
}
@Test
void test_savePremiumList_clearsCache() {
assertThat(PremiumListDatastoreDao.premiumListCache.getIfPresent("tld")).isNull();
PremiumList pl = PremiumListDatastoreDao.getLatestRevision("tld").get();
assertThat(PremiumListDatastoreDao.premiumListCache.getIfPresent("tld").get()).isEqualTo(pl);
transactIfJpaTm(() -> PremiumListDatastoreDao.save("tld", ImmutableList.of("test,USD 1")));
assertThat(PremiumListDatastoreDao.premiumListCache.getIfPresent("tld")).isNull();
}
/** Persists a premium list with a specified number of nonsense entries. */

View file

@ -0,0 +1,139 @@
// Copyright 2021 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.model.registry.label;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.newRegistry;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.joda.time.Duration.standardDays;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.truth.Truth8;
import google.registry.dns.writer.VoidDnsWriter;
import google.registry.model.pricing.StaticPremiumListPricingEngine;
import google.registry.model.registry.Registry;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestCacheExtension;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link PremiumListDualDao}. */
@DualDatabaseTest
public class PremiumListDualDaoTest {
@RegisterExtension
public final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
// Set long persist times on caches so they can be tested (cache times default to 0 in tests).
@RegisterExtension
public final TestCacheExtension testCacheExtension =
new TestCacheExtension.Builder()
.withPremiumListsCache(standardDays(1))
.withPremiumListEntriesCache(standardDays(1))
.build();
@BeforeEach
void before() {
createTld("tld");
}
@TestOfyOnly
void testGetPremiumPrice_secondaryLoadMissingSql() {
PremiumListSqlDao.delete(PremiumListSqlDao.getLatestRevision("tld").get());
assertThat(
assertThrows(
IllegalStateException.class,
() -> PremiumListDualDao.getPremiumPrice("brass", Registry.get("tld"))))
.hasMessageThat()
.isEqualTo(
"Unequal prices for domain brass.tld from primary Datastore DB "
+ "(Optional[USD 20.00]) and secondary SQL db (Optional.empty).");
}
@TestSqlOnly
void testGetPremiumPrice_secondaryLoadMissingOfy() {
PremiumList premiumList = PremiumListDatastoreDao.getLatestRevision("tld").get();
PremiumListDatastoreDao.delete(premiumList);
assertThat(
assertThrows(
IllegalStateException.class,
() -> PremiumListDualDao.getPremiumPrice("brass", Registry.get("tld"))))
.hasMessageThat()
.isEqualTo(
"Unequal prices for domain brass.tld from primary SQL DB (Optional[USD 20.00]) "
+ "and secondary Datastore db (Optional.empty).");
}
@TestOfyOnly
void testGetPremiumPrice_secondaryDifferentSql() {
PremiumListSqlDao.save("tld", ImmutableList.of("brass,USD 50"));
assertThat(
assertThrows(
IllegalStateException.class,
() -> PremiumListDualDao.getPremiumPrice("brass", Registry.get("tld"))))
.hasMessageThat()
.isEqualTo(
"Unequal prices for domain brass.tld from primary Datastore DB "
+ "(Optional[USD 20.00]) and secondary SQL db (Optional[USD 50.00]).");
}
@TestSqlOnly
void testGetPremiumPrice_secondaryDifferentOfy() {
PremiumListDatastoreDao.save("tld", ImmutableList.of("brass,USD 50"));
assertThat(
assertThrows(
IllegalStateException.class,
() -> PremiumListDualDao.getPremiumPrice("brass", Registry.get("tld"))))
.hasMessageThat()
.isEqualTo(
"Unequal prices for domain brass.tld from primary SQL DB "
+ "(Optional[USD 20.00]) and secondary Datastore db (Optional[USD 50.00]).");
}
@TestOfyAndSql
void testGetPremiumPrice_returnsNoPriceWhenNoPremiumListConfigured() {
createTld("ghost");
persistResource(
new Registry.Builder()
.setTldStr("ghost")
.setPremiumPricingEngine(StaticPremiumListPricingEngine.NAME)
.setDnsWriters(ImmutableSet.of(VoidDnsWriter.NAME))
.build());
assertThat(Registry.get("ghost").getPremiumList()).isNull();
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("blah", Registry.get("ghost"))).isEmpty();
}
@TestOfyAndSql
void testGetPremiumPrice_emptyWhenPremiumListDeleted() {
PremiumList toDelete = PremiumListDualDao.getLatestRevision("tld").get();
PremiumListDualDao.delete(toDelete);
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("blah", Registry.get("tld"))).isEmpty();
}
@TestOfyAndSql
void getPremiumPrice_returnsNoneWhenNoPremiumListConfigured() {
persistResource(newRegistry("foobar", "FOOBAR").asBuilder().setPremiumList(null).build());
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("rich", Registry.get("foobar"))).isEmpty();
}
}

View file

@ -69,7 +69,7 @@ public class PremiumListTest {
@Test
void testProbablePremiumLabels() {
PremiumList pl = PremiumList.getUncached("tld").get();
PremiumList pl = PremiumListDualDao.getLatestRevision("tld").get();
PremiumListRevision revision = ofy().load().key(pl.getRevisionKey()).now();
assertThat(revision.getProbablePremiumLabels().mightContain("notpremium")).isFalse();
for (String label : ImmutableList.of("rich", "lol", "johnny-be-goode", "icann")) {
@ -85,7 +85,7 @@ public class PremiumListTest {
assertThrows(
IllegalStateException.class,
() ->
PremiumList.getUncached("tld")
PremiumListDualDao.getLatestRevision("tld")
.get()
.parse(
ImmutableList.of(

View file

@ -42,7 +42,7 @@ import google.registry.schema.integration.SqlIntegrationTestSuite.BeforeSuiteTes
import google.registry.schema.registrar.RegistrarDaoTest;
import google.registry.schema.replay.SqlReplayCheckpointTest;
import google.registry.schema.server.LockDaoTest;
import google.registry.schema.tld.PremiumListDaoTest;
import google.registry.schema.tld.PremiumListSqlDaoTest;
import google.registry.testing.AppEngineExtension;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@ -92,7 +92,7 @@ import org.junit.runner.RunWith;
KmsSecretRevisionSqlDaoTest.class,
LockDaoTest.class,
PollMessageTest.class,
PremiumListDaoTest.class,
PremiumListSqlDaoTest.class,
RdeRevisionTest.class,
RegistrarDaoTest.class,
RegistryTest.class,

View file

@ -18,30 +18,30 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.DatabaseHelper.newRegistry;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.joda.money.CurrencyUnit.JPY;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.joda.time.Duration.standardDays;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.googlecode.objectify.Key;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.TestCacheExtension;
import java.math.BigDecimal;
import java.util.Optional;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link PremiumListDao}. */
public class PremiumListDaoTest {
/** Unit tests for {@link PremiumListSqlDao}. */
public class PremiumListSqlDaoTest {
private final FakeClock fakeClock = new FakeClock();
@ -53,6 +53,14 @@ public class PremiumListDaoTest {
.withClock(fakeClock)
.build();
// Set long persist times on caches so they can be tested (cache times default to 0 in tests).
@RegisterExtension
public final TestCacheExtension testCacheExtension =
new TestCacheExtension.Builder()
.withPremiumListsCache(standardDays(1))
.withPremiumListEntriesCache(standardDays(1))
.build();
private ImmutableMap<String, BigDecimal> testPrices;
private PremiumList testList;
@ -78,11 +86,12 @@ public class PremiumListDaoTest {
@Test
void saveNew_worksSuccessfully() {
PremiumListDao.saveNew(testList);
PremiumListSqlDao.save(testList);
jpaTm()
.transact(
() -> {
Optional<PremiumList> persistedListOpt = PremiumListDao.getLatestRevision("testname");
Optional<PremiumList> persistedListOpt =
PremiumListSqlDao.getLatestRevision("testname");
assertThat(persistedListOpt).isPresent();
PremiumList persistedList = persistedListOpt.get();
assertThat(persistedList.getLabelsToPrices()).containsExactlyEntriesIn(testPrices);
@ -92,17 +101,17 @@ public class PremiumListDaoTest {
@Test
void update_worksSuccessfully() {
PremiumListDao.saveNew(testList);
Optional<PremiumList> persistedList = PremiumListDao.getLatestRevision("testname");
PremiumListSqlDao.save(testList);
Optional<PremiumList> persistedList = PremiumListSqlDao.getLatestRevision("testname");
assertThat(persistedList).isPresent();
long firstRevisionId = persistedList.get().getRevisionId();
PremiumListDao.update(
PremiumListSqlDao.save(
new PremiumList.Builder()
.setName("testname")
.setCurrency(USD)
.setLabelsToPrices(
ImmutableMap.of(
"update",
"save",
BigDecimal.valueOf(55343.12),
"new",
BigDecimal.valueOf(0.01),
@ -113,65 +122,46 @@ public class PremiumListDaoTest {
jpaTm()
.transact(
() -> {
Optional<PremiumList> updatedListOpt = PremiumListDao.getLatestRevision("testname");
assertThat(updatedListOpt).isPresent();
PremiumList updatedList = updatedListOpt.get();
assertThat(updatedList.getLabelsToPrices())
Optional<PremiumList> savedListOpt = PremiumListSqlDao.getLatestRevision("testname");
assertThat(savedListOpt).isPresent();
PremiumList savedList = savedListOpt.get();
assertThat(savedList.getLabelsToPrices())
.containsExactlyEntriesIn(
ImmutableMap.of(
"update",
"save",
BigDecimal.valueOf(55343.12),
"new",
BigDecimal.valueOf(0.01),
"silver",
BigDecimal.valueOf(30.03)));
assertThat(updatedList.getCreationTime()).isEqualTo(fakeClock.nowUtc());
assertThat(updatedList.getRevisionId()).isGreaterThan(firstRevisionId);
assertThat(updatedList.getCreationTime()).isEqualTo(fakeClock.nowUtc());
assertThat(savedList.getCreationTime()).isEqualTo(fakeClock.nowUtc());
assertThat(savedList.getRevisionId()).isGreaterThan(firstRevisionId);
assertThat(savedList.getCreationTime()).isEqualTo(fakeClock.nowUtc());
});
}
@Test
void saveNew_throwsWhenPremiumListAlreadyExists() {
PremiumListDao.saveNew(testList);
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> PremiumListDao.saveNew(testList));
assertThat(thrown).hasMessageThat().isEqualTo("Premium list 'testname' already exists");
}
// TODO(b/147246613): Un-ignore this.
@Test
@Disabled
void update_throwsWhenListDoesntExist() {
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> PremiumListDao.update(testList));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Can't update non-existent premium list 'testname'");
}
@Test
void checkExists_worksSuccessfully() {
assertThat(PremiumListDao.checkExists("testname")).isFalse();
PremiumListDao.saveNew(testList);
assertThat(PremiumListDao.checkExists("testname")).isTrue();
assertThat(PremiumListSqlDao.getLatestRevision("testname")).isEmpty();
PremiumListSqlDao.save(testList);
assertThat(PremiumListSqlDao.getLatestRevision("testname")).isPresent();
}
@Test
void getLatestRevision_returnsEmptyForNonexistentList() {
assertThat(PremiumListDao.getLatestRevision("nonexistentlist")).isEmpty();
assertThat(PremiumListSqlDao.getLatestRevision("nonexistentlist")).isEmpty();
}
@Test
void getLatestRevision_worksSuccessfully() {
PremiumListDao.saveNew(
PremiumListSqlDao.save(
new PremiumList.Builder()
.setName("list1")
.setCurrency(JPY)
.setLabelsToPrices(ImmutableMap.of("wrong", BigDecimal.valueOf(1000.50)))
.setCreationTime(fakeClock.nowUtc())
.build());
PremiumListDao.update(
PremiumListSqlDao.save(
new PremiumList.Builder()
.setName("list1")
.setCurrency(JPY)
@ -181,7 +171,7 @@ public class PremiumListDaoTest {
jpaTm()
.transact(
() -> {
Optional<PremiumList> persistedList = PremiumListDao.getLatestRevision("list1");
Optional<PremiumList> persistedList = PremiumListSqlDao.getLatestRevision("list1");
assertThat(persistedList).isPresent();
assertThat(persistedList.get().getName()).isEqualTo("list1");
assertThat(persistedList.get().getCurrency()).isEqualTo(JPY);
@ -190,12 +180,6 @@ public class PremiumListDaoTest {
});
}
@Test
void getPremiumPrice_returnsNoneWhenNoPremiumListConfigured() {
persistResource(newRegistry("foobar", "FOOBAR").asBuilder().setPremiumList(null).build());
assertThat(PremiumListDao.getPremiumPrice("rich", Registry.get("foobar"))).isEmpty();
}
@Test
void getPremiumPrice_worksSuccessfully() {
persistResource(
@ -207,28 +191,18 @@ public class PremiumListDaoTest {
google.registry.model.registry.label.PremiumList.class,
"premlist"))
.build());
PremiumListDao.saveNew(
PremiumListSqlDao.save(
new PremiumList.Builder()
.setName("premlist")
.setCurrency(USD)
.setLabelsToPrices(testPrices)
.setCreationTime(fakeClock.nowUtc())
.build());
assertThat(PremiumListDao.getPremiumPrice("silver", Registry.get("foobar")))
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "silver"))
.hasValue(Money.of(USD, 10.23));
assertThat(PremiumListDao.getPremiumPrice("gold", Registry.get("foobar")))
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "gold"))
.hasValue(Money.of(USD, 1305.47));
assertThat(PremiumListDao.getPremiumPrice("zirconium", Registry.get("foobar"))).isEmpty();
}
@Test
void testGetPremiumPrice_throwsWhenPremiumListCantBeLoaded() {
createTld("tld");
IllegalStateException thrown =
assertThrows(
IllegalStateException.class,
() -> PremiumListDao.getPremiumPrice("foobar", Registry.get("tld")));
assertThat(thrown).hasMessageThat().isEqualTo("Could not load premium list 'tld'");
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "zirconium")).isEmpty();
}
@Test
@ -242,7 +216,7 @@ public class PremiumListDaoTest {
google.registry.model.registry.label.PremiumList.class,
"premlist"))
.build());
PremiumListDao.saveNew(
PremiumListSqlDao.save(
new PremiumList.Builder()
.setName("premlist")
.setCurrency(JPY)
@ -256,14 +230,22 @@ public class PremiumListDaoTest {
BigDecimal.valueOf(15000)))
.setCreationTime(fakeClock.nowUtc())
.build());
assertThat(PremiumListDao.getPremiumPrice("silver", Registry.get("foobar")))
.hasValue(moneyOf(JPY, 10));
assertThat(PremiumListDao.getPremiumPrice("gold", Registry.get("foobar")))
.hasValue(moneyOf(JPY, 1000));
assertThat(PremiumListDao.getPremiumPrice("palladium", Registry.get("foobar")))
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "silver")).hasValue(moneyOf(JPY, 10));
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "gold")).hasValue(moneyOf(JPY, 1000));
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "palladium"))
.hasValue(moneyOf(JPY, 15000));
}
@Test
void test_savePremiumList_clearsCache() {
assertThat(PremiumListSqlDao.premiumListCache.getIfPresent("testname")).isNull();
PremiumListSqlDao.save(testList);
PremiumList pl = PremiumListSqlDao.getLatestRevision("testname").get();
assertThat(PremiumListSqlDao.premiumListCache.getIfPresent("testname").get()).isEqualTo(pl);
transactIfJpaTm(() -> PremiumListSqlDao.save("testname", ImmutableList.of("test,USD 1")));
assertThat(PremiumListSqlDao.premiumListCache.getIfPresent("testname")).isNull();
}
private static Money moneyOf(CurrencyUnit unit, double amount) {
return Money.of(unit, BigDecimal.valueOf(amount).setScale(unit.getDecimalPlaces()));
}

View file

@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.schema.tld.PremiumListUtils.parseToPremiumList;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import google.registry.model.registry.label.PremiumList;
import google.registry.testing.AppEngineExtension;
import java.math.BigDecimal;
@ -34,7 +35,8 @@ class PremiumListUtilsTest {
@Test
void parseInputToPremiumList_works() {
PremiumList premiumList =
parseToPremiumList("testlist", "foo,USD 99.50\n" + "bar,USD 30\n" + "baz,USD 10\n");
parseToPremiumList(
"testlist", ImmutableList.of("foo,USD 99.50", "bar,USD 30", "baz,USD 10"));
assertThat(premiumList.getName()).isEqualTo("testlist");
assertThat(premiumList.getLabelsToPrices())
.containsExactly("foo", twoDigits(99.50), "bar", twoDigits(30), "baz", twoDigits(10));
@ -47,7 +49,7 @@ class PremiumListUtilsTest {
IllegalArgumentException.class,
() ->
parseToPremiumList(
"testlist", "foo,USD 99.50\n" + "bar,USD 30\n" + "baz,JPY 990\n"));
"testlist", ImmutableList.of("foo,USD 99.50", "bar,USD 30", "baz,JPY 990")));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("The Cloud SQL schema requires exactly one currency, but got: [JPY, USD]");

View file

@ -32,7 +32,9 @@ import static google.registry.model.ImmutableObjectSubject.immutableObjectCorres
import static google.registry.model.ResourceTransferUtils.createTransferResponse;
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.model.registry.label.PremiumListDatastoreDao.parentPremiumListEntriesOnRevision;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.ofyTmOrDoNothing;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
@ -57,6 +59,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import com.google.common.net.InetAddresses;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.cmd.Saver;
@ -100,6 +103,7 @@ import google.registry.model.registry.Registry.TldType;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumList.PremiumListRevision;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.model.registry.label.ReservedList;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.ContactTransferData;
@ -108,11 +112,11 @@ import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
import google.registry.tmch.LordnTaskUtils;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nullable;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
@ -387,25 +391,23 @@ public class DatabaseHelper {
.build();
PremiumListRevision revision = PremiumListRevision.create(premiumList, entries.keySet());
if (tm().isOfy()) {
ImmutableList<Object> premiumLists =
ImmutableList.of(
premiumList.asBuilder().setRevision(Key.create(revision)).build(), revision);
ImmutableSet<PremiumListEntry> entriesOnRevision =
parentPremiumListEntriesOnRevision(entries.values(), Key.create(revision));
if (alwaysSaveWithBackup) {
tm().transact(
() -> {
tm().putAll(premiumLists);
tm().putAll(entriesOnRevision);
});
} else {
tm().putAllWithoutBackup(premiumLists);
tm().putAllWithoutBackup(entriesOnRevision);
}
ImmutableList<Object> premiumListOfyObjects =
ImmutableList.of(
premiumList.asBuilder().setRevision(Key.create(revision)).build(), revision);
ImmutableSet<PremiumListEntry> entriesOnRevision =
parentPremiumListEntriesOnRevision(entries.values(), Key.create(revision));
if (alwaysSaveWithBackup) {
ofyTm()
.transact(
() -> {
ofyTm().putAll(premiumListOfyObjects);
ofyTm().putAll(entriesOnRevision);
});
} else {
tm().transact(() -> tm().insert(premiumList));
ofyTm().putAllWithoutBackup(premiumListOfyObjects);
ofyTm().putAllWithoutBackup(entriesOnRevision);
}
jpaTm().transact(() -> jpaTm().insert(premiumList));
maybeAdvanceClock();
// 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
@ -807,7 +809,7 @@ public class DatabaseHelper {
/** Assert that the expected billing events are exactly the ones found in the fake Datastore. */
public static void assertBillingEvents(BillingEvent... expected) {
assertBillingEventsEqual(getBillingEvents(), Arrays.asList(expected));
assertBillingEventsEqual(getBillingEvents(), asList(expected));
}
/** Assert that the expected billing events set is exactly the one found in the fake Datastore. */
@ -820,7 +822,7 @@ public class DatabaseHelper {
*/
public static void assertBillingEventsForResource(
EppResource resource, BillingEvent... expected) {
assertBillingEventsEqual(getBillingEvents(resource), Arrays.asList(expected));
assertBillingEventsEqual(getBillingEvents(resource), asList(expected));
}
/** Assert that there are no billing events. */
@ -851,15 +853,15 @@ public class DatabaseHelper {
}
public static void assertPollMessages(String clientId, PollMessage... expected) {
assertPollMessagesEqual(getPollMessages(clientId), Arrays.asList(expected));
assertPollMessagesEqual(getPollMessages(clientId), asList(expected));
}
public static void assertPollMessages(PollMessage... expected) {
assertPollMessagesEqual(getPollMessages(), Arrays.asList(expected));
assertPollMessagesEqual(getPollMessages(), asList(expected));
}
public static void assertPollMessagesForResource(DomainContent domain, PollMessage... expected) {
assertPollMessagesEqual(getPollMessages(domain), Arrays.asList(expected));
assertPollMessagesEqual(getPollMessages(domain), asList(expected));
}
public static ImmutableList<PollMessage> getPollMessages() {
@ -1232,19 +1234,8 @@ public class DatabaseHelper {
/** Returns the entire map of {@link PremiumListEntry}s for the given {@link PremiumList}. */
public static ImmutableMap<String, PremiumListEntry> loadPremiumListEntries(
PremiumList premiumList) {
try {
ImmutableMap.Builder<String, PremiumListEntry> entriesMap = new ImmutableMap.Builder<>();
if (premiumList.getRevisionKey() != null) {
for (PremiumListEntry entry :
ofy().load().type(PremiumListEntry.class).ancestor(premiumList.getRevisionKey())) {
entriesMap.put(entry.getLabel(), entry);
}
}
return entriesMap.build();
} catch (Exception e) {
throw new RuntimeException(
"Could not retrieve entries for premium list " + premiumList.getName(), e);
}
return Streams.stream(PremiumListDualDao.loadAllPremiumListEntries(premiumList.getName()))
.collect(toImmutableMap(PremiumListEntry::getLabel, Function.identity()));
}
/** Loads and returns the registrar with the given client ID, or throws IAE if not present. */

View file

@ -18,7 +18,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import google.registry.model.EppResource;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDatastoreDao;
import google.registry.schema.tld.PremiumListSqlDao;
import java.util.Map;
import java.util.Optional;
import org.joda.time.Duration;
@ -70,15 +71,18 @@ public class TestCacheExtension implements BeforeEachCallback, AfterEachCallback
public Builder withPremiumListsCache(Duration expiry) {
cacheHandlerMap.put(
"PremiumList.cachePremiumLists",
new TestCacheHandler(PremiumList::setPremiumListCacheForTest, expiry));
"PremiumListSqlDao.premiumListCache",
new TestCacheHandler(PremiumListSqlDao::setPremiumListCacheForTest, expiry));
cacheHandlerMap.put(
"PremiumListDatastoreDao.premiumListCache",
new TestCacheHandler(PremiumListDatastoreDao::setPremiumListCacheForTest, expiry));
return this;
}
public Builder withPremiumListEntriesCache(Duration expiry) {
cacheHandlerMap.put(
"PremiumList.cachePremiumListEntries",
new TestCacheHandler(PremiumList::setPremiumListEntriesCacheForTest, expiry));
new TestCacheHandler(PremiumListDatastoreDao::setPremiumListEntriesCacheForTest, expiry));
return this;
}

View file

@ -22,7 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import com.beust.jcommander.ParameterException;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.testing.DeterministicStringGenerator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -101,7 +101,7 @@ class CreateDomainCommandTest extends EppToolCommandTestCase<CreateDomainCommand
persistResource(
Registry.get("baar")
.asBuilder()
.setPremiumList(PremiumList.getUncached("baar").get())
.setPremiumList(PremiumListDualDao.getLatestRevision("baar").get())
.build());
runCommandForced(
"--client=NewRegistrar",

View file

@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumListDualDao;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link DeletePremiumListCommand}. */
@ -36,7 +37,7 @@ class DeletePremiumListCommandTest extends CommandTestCase<DeletePremiumListComm
PremiumList premiumList = persistPremiumList("xn--q9jyb4c", "blah,USD 100");
assertThat(loadPremiumListEntries(premiumList)).hasSize(1);
runCommand("--force", "--name=xn--q9jyb4c");
assertThat(PremiumList.getUncached("xn--q9jyb4c")).isEmpty();
assertThat(PremiumListDualDao.getLatestRevision("xn--q9jyb4c")).isEmpty();
// Ensure that the Datastore premium list entry entities were deleted correctly.
assertThat(ofy().load()
@ -64,7 +65,7 @@ class DeletePremiumListCommandTest extends CommandTestCase<DeletePremiumListComm
assertThrows(
IllegalArgumentException.class,
() -> runCommandForced("--name=" + premiumList.getName()));
assertThat(PremiumList.getUncached(premiumList.getName())).isPresent();
assertThat(PremiumListDualDao.getLatestRevision(premiumList.getName())).isPresent();
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Cannot delete premium list because it is used on these tld(s): xn--q9jyb4c");

View file

@ -16,14 +16,13 @@ package google.registry.tools.server;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.registry.label.PremiumListUtils.deletePremiumList;
import static google.registry.model.registry.label.PremiumListUtils.getPremiumPrice;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.loadPremiumListEntries;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.FakeJsonResponse;
import org.joda.money.Money;
@ -46,7 +45,7 @@ public class CreatePremiumListActionTest {
@BeforeEach
void beforeEach() {
createTlds("foo", "xn--q9jyb4c", "how");
deletePremiumList(PremiumList.getUncached("foo").get());
PremiumListDualDao.delete(PremiumListDualDao.getLatestRevision("foo").get());
action = new CreatePremiumListAction();
response = new FakeJsonResponse();
action.response = response;
@ -78,7 +77,8 @@ public class CreatePremiumListActionTest {
action.override = true;
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(loadPremiumListEntries(PremiumList.getUncached("zanzibar").get())).hasSize(1);
assertThat(loadPremiumListEntries(PremiumListDualDao.getLatestRevision("zanzibar").get()))
.hasSize(1);
}
@Test
@ -87,8 +87,10 @@ public class CreatePremiumListActionTest {
action.inputData = "rich,USD 25\nricher,USD 1000\n";
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(loadPremiumListEntries(PremiumList.getUncached("foo").get())).hasSize(2);
assertThat(getPremiumPrice("rich", Registry.get("foo"))).hasValue(Money.parse("USD 25"));
assertThat(getPremiumPrice("diamond", Registry.get("foo"))).isEmpty();
PremiumList premiumList = PremiumListDualDao.getLatestRevision("foo").get();
assertThat(loadPremiumListEntries(premiumList)).hasSize(2);
assertThat(PremiumListDualDao.getPremiumPrice("rich", Registry.get("foo")))
.hasValue(Money.parse("USD 25"));
assertThat(PremiumListDualDao.getPremiumPrice("diamond", Registry.get("foo"))).isEmpty();
}
}

View file

@ -15,22 +15,23 @@
package google.registry.tools.server;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.registry.label.PremiumListUtils.getPremiumPrice;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.schema.tld.PremiumListUtils.parseToPremiumList;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.loadPremiumListEntries;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.base.Splitter;
import com.google.common.truth.Truth8;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList;
import google.registry.schema.tld.PremiumListDao;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeJsonResponse;
import java.math.BigDecimal;
import java.util.List;
import org.joda.money.Money;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -75,24 +76,31 @@ class UpdatePremiumListActionTest {
@Test
void test_success() {
PremiumListDao.saveNew(
parseToPremiumList(
"foo", readResourceUtf8(DatabaseHelper.class, "default_premium_list_testdata.csv")));
List<String> inputLines =
Splitter.on('\n')
.omitEmptyStrings()
.splitToList(
readResourceUtf8(DatabaseHelper.class, "default_premium_list_testdata.csv"));
PremiumListDualDao.save("foo", inputLines);
action.name = "foo";
action.inputData = "rich,USD 75\nricher,USD 5000\npoor, USD 0.99";
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
Registry registry = Registry.get("foo");
assertThat(loadPremiumListEntries(PremiumList.getUncached("foo").get())).hasSize(3);
assertThat(getPremiumPrice("rich", registry)).hasValue(Money.parse("USD 75"));
assertThat(getPremiumPrice("richer", registry)).hasValue(Money.parse("USD 5000"));
assertThat(getPremiumPrice("poor", registry)).hasValue(Money.parse("USD 0.99"));
assertThat(getPremiumPrice("diamond", registry)).isEmpty();
assertThat(loadPremiumListEntries(PremiumListDualDao.getLatestRevision("foo").get()))
.hasSize(3);
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("rich", registry))
.hasValue(Money.parse("USD 75"));
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("richer", registry))
.hasValue(Money.parse("USD 5000"));
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("poor", registry))
.hasValue(Money.parse("USD 0.99"));
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("diamond", registry)).isEmpty();
jpaTm()
.transact(
() -> {
PremiumList persistedList = PremiumListDao.getLatestRevision("foo").get();
PremiumList persistedList = PremiumListSqlDao.getLatestRevision("foo").get();
assertThat(persistedList.getLabelsToPrices())
.containsEntry("rich", new BigDecimal("75.00"));
assertThat(persistedList.getLabelsToPrices())