Add initial support to write/update reserved lists to Cloud SQL (#388)

* Add initial support to write reserved list to Cloud SQL

* Add support to update reserved list in Cloud SQL

* Fix wrong check when override is enabled in create command

* Add sql dependent tests to the suite

* Address comment

* Make invocation of super.execute more readable

* Add test to check upper case and non-puny code label

* Move ReservedListDao related classes to schema package

* Simplify a function name
This commit is contained in:
Shicong Huang 2019-12-19 12:51:48 -05:00 committed by GitHub
parent f301314d97
commit f76030f7f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 573 additions and 12 deletions

View file

@ -121,7 +121,7 @@ public abstract class BaseDomainLabelList<T extends Comparable<?>, R extends Dom
* (sans comment) and the comment (in that order). If the line was blank or empty, then this * (sans comment) and the comment (in that order). If the line was blank or empty, then this
* method returns an empty list. * method returns an empty list.
*/ */
protected static List<String> splitOnComment(String line) { public static List<String> splitOnComment(String line) {
String comment = ""; String comment = "";
int index = line.indexOf('#'); int index = line.indexOf('#');
if (index != -1) { if (index != -1) {

View file

@ -14,13 +14,20 @@
package google.registry.schema.tld; package google.registry.schema.tld;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.sortedCopyOf;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import google.registry.model.CreateAutoTimestamp; import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import google.registry.model.registry.label.ReservationType; import google.registry.model.registry.label.ReservationType;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.persistence.CollectionTable; import javax.persistence.CollectionTable;
import javax.persistence.Column; import javax.persistence.Column;
@ -114,6 +121,22 @@ public class ReservedList extends ImmutableObject {
/** Constructs a {@link ReservedList} object. */ /** Constructs a {@link ReservedList} object. */
public static ReservedList create( public static ReservedList create(
String name, Boolean shouldPublish, Map<String, ReservedEntry> labelsToReservations) { String name, Boolean shouldPublish, Map<String, ReservedEntry> labelsToReservations) {
ImmutableList<String> invalidLabels =
labelsToReservations.entrySet().parallelStream()
.flatMap(
entry -> {
String label = entry.getKey();
if (label.equals(canonicalizeDomainName(label))) {
return Stream.empty();
} else {
return Stream.of(label);
}
})
.collect(toImmutableList());
checkArgument(
invalidLabels.isEmpty(),
"Label(s) [%s] must be in puny-coded, lower-case form",
Joiner.on(",").join(sortedCopyOf(invalidLabels)));
return new ReservedList(name, shouldPublish, labelsToReservations); return new ReservedList(name, shouldPublish, labelsToReservations);
} }

View file

@ -0,0 +1,47 @@
// Copyright 2019 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.schema.tld;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
/** Data access object class for {@link ReservedList} */
public class ReservedListDao {
/** Persist a new reserved list to Cloud SQL. */
public static void save(ReservedList reservedList) {
jpaTm().transact(() -> jpaTm().getEntityManager().persist(reservedList));
}
/**
* Returns whether the reserved list of the given name exists.
*
* <p>This means that at least one reserved list revision must exist for the given name.
*/
public static boolean checkExists(String reservedListName) {
return jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery("SELECT 1 FROM ReservedList WHERE name = :name", Integer.class)
.setParameter("name", reservedListName)
.setMaxResults(1)
.getResultList()
.size()
> 0);
}
private ReservedListDao() {}
}

View file

@ -14,9 +14,23 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.registry.label.BaseDomainLabelList.splitOnComment;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.google.common.base.Splitter;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.flogger.FluentLogger;
import google.registry.model.registry.label.ReservationType;
import google.registry.schema.tld.ReservedList.ReservedEntry;
import google.registry.tools.params.PathParameter; import google.registry.tools.params.PathParameter;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
@ -25,6 +39,8 @@ import javax.annotation.Nullable;
*/ */
public abstract class CreateOrUpdateReservedListCommand extends MutatingCommand { public abstract class CreateOrUpdateReservedListCommand extends MutatingCommand {
static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Nullable @Nullable
@Parameter( @Parameter(
names = {"-n", "--name"}, names = {"-n", "--name"},
@ -45,4 +61,81 @@ public abstract class CreateOrUpdateReservedListCommand extends MutatingCommand
"Whether the list is published to the concatenated list on Drive (defaults to true).", "Whether the list is published to the concatenated list on Drive (defaults to true).",
arity = 1) arity = 1)
Boolean shouldPublish; Boolean shouldPublish;
@Parameter(
names = {"--also_cloud_sql"},
description =
"Persist reserved list to Cloud SQL in addition to Datastore; defaults to false.")
boolean alsoCloudSql;
google.registry.schema.tld.ReservedList cloudSqlReservedList;
abstract void saveToCloudSql();
@Override
protected String execute() throws Exception {
// Save the list to Datastore and output its response.
String output = super.execute();
logger.atInfo().log(output);
String cloudSqlMessage;
if (alsoCloudSql) {
cloudSqlMessage =
String.format(
"Saved reserved list %s with %d entries",
name, cloudSqlReservedList.getLabelsToReservations().size());
try {
logger.atInfo().log("Saving reserved list to Cloud SQL for TLD %s", name);
saveToCloudSql();
logger.atInfo().log(cloudSqlMessage);
} catch (Throwable e) {
cloudSqlMessage =
"Unexpected error saving reserved list to Cloud SQL from nomulus tool command";
logger.atSevere().withCause(e).log(cloudSqlMessage);
}
} else {
cloudSqlMessage = "Persisting reserved list to Cloud SQL is not enabled";
}
return cloudSqlMessage;
}
/** Turns the list CSV data into a map of labels to {@link ReservedEntry}. */
static ImmutableMap<String, ReservedEntry> parseToReservationsByLabels(Iterable<String> lines) {
Map<String, ReservedEntry> labelsToEntries = Maps.newHashMap();
Multiset<String> duplicateLabels = HashMultiset.create();
for (String originalLine : lines) {
List<String> lineAndComment = splitOnComment(originalLine);
if (lineAndComment.isEmpty()) {
continue;
}
String line = lineAndComment.get(0);
String comment = lineAndComment.get(1);
List<String> parts = Splitter.on(',').trimResults().splitToList(line);
checkArgument(
parts.size() == 2 || parts.size() == 3,
"Could not parse line in reserved list: %s",
originalLine);
String label = parts.get(0);
checkArgument(
label.equals(canonicalizeDomainName(label)),
"Label '%s' must be in puny-coded, lower-case form",
label);
ReservationType reservationType = ReservationType.valueOf(parts.get(1));
ReservedEntry reservedEntry = ReservedEntry.create(reservationType, comment);
// Check if the label was already processed for this list (which is an error), and if so,
// accumulate it so that a list of all duplicates can be thrown.
if (labelsToEntries.containsKey(label)) {
duplicateLabels.add(label, duplicateLabels.contains(label) ? 1 : 2);
} else {
labelsToEntries.put(label, reservedEntry);
}
}
if (!duplicateLabels.isEmpty()) {
throw new IllegalStateException(
String.format(
"Reserved list cannot contain duplicate labels. Dupes (with counts) were: %s",
duplicateLabels));
}
return ImmutableMap.copyOf(labelsToEntries);
}
} }

View file

@ -16,6 +16,7 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.registry.Registries.assertTldExists; import static google.registry.model.registry.Registries.assertTldExists;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.ListNamingUtils.convertFilePathToName; import static google.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC; import static org.joda.time.DateTimeZone.UTC;
@ -26,6 +27,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import google.registry.model.registry.label.ReservedList; import google.registry.model.registry.label.ReservedList;
import google.registry.schema.tld.ReservedListDao;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.List; import java.util.List;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -54,15 +56,35 @@ final class CreateReservedListCommand extends CreateOrUpdateReservedListCommand
validateListName(name); validateListName(name);
} }
DateTime now = DateTime.now(UTC); DateTime now = DateTime.now(UTC);
List<String> allLines = Files.readAllLines(input, UTF_8);
boolean shouldPublish = this.shouldPublish == null || this.shouldPublish;
ReservedList reservedList = ReservedList reservedList =
new ReservedList.Builder() new ReservedList.Builder()
.setName(name) .setName(name)
.setReservedListMapFromLines(Files.readAllLines(input, UTF_8)) .setReservedListMapFromLines(allLines)
.setShouldPublish(shouldPublish == null || shouldPublish) .setShouldPublish(shouldPublish)
.setCreationTime(now) .setCreationTime(now)
.setLastUpdateTime(now) .setLastUpdateTime(now)
.build(); .build();
stageEntityChange(null, reservedList); stageEntityChange(null, reservedList);
if (alsoCloudSql) {
cloudSqlReservedList =
google.registry.schema.tld.ReservedList.create(
name, shouldPublish, parseToReservationsByLabels(allLines));
}
}
@Override
void saveToCloudSql() {
jpaTm()
.transact(
() -> {
checkArgument(
!ReservedListDao.checkExists(cloudSqlReservedList.getName()),
"A reserved list of this name already exists: %s.",
cloudSqlReservedList.getName());
ReservedListDao.save(cloudSqlReservedList);
});
} }
private static void validateListName(String name) { private static void validateListName(String name) {

View file

@ -15,14 +15,17 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.ListNamingUtils.convertFilePathToName; import static google.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import google.registry.model.registry.label.ReservedList; import google.registry.model.registry.label.ReservedList;
import google.registry.schema.tld.ReservedListDao;
import google.registry.util.SystemClock; import google.registry.util.SystemClock;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.List;
import java.util.Optional; import java.util.Optional;
/** Command to safely update {@link ReservedList} on Datastore. */ /** Command to safely update {@link ReservedList} on Datastore. */
@ -32,18 +35,38 @@ final class UpdateReservedListCommand extends CreateOrUpdateReservedListCommand
@Override @Override
protected void init() throws Exception { protected void init() throws Exception {
name = Strings.isNullOrEmpty(name) ? convertFilePathToName(input) : name; name = Strings.isNullOrEmpty(name) ? convertFilePathToName(input) : name;
// TODO(shicong): Read existing entry from Cloud SQL
Optional<ReservedList> existing = ReservedList.get(name); Optional<ReservedList> existing = ReservedList.get(name);
checkArgument( checkArgument(
existing.isPresent(), "Could not update reserved list %s because it doesn't exist.", name); existing.isPresent(), "Could not update reserved list %s because it doesn't exist.", name);
boolean shouldPublish =
this.shouldPublish == null ? existing.get().getShouldPublish() : this.shouldPublish;
List<String> allLines = Files.readAllLines(input, UTF_8);
ReservedList.Builder updated = ReservedList.Builder updated =
existing existing
.get() .get()
.asBuilder() .asBuilder()
.setReservedListMapFromLines(Files.readAllLines(input, UTF_8)) .setReservedListMapFromLines(allLines)
.setLastUpdateTime(new SystemClock().nowUtc()); .setLastUpdateTime(new SystemClock().nowUtc())
if (shouldPublish != null) { .setShouldPublish(shouldPublish);
updated.setShouldPublish(shouldPublish);
}
stageEntityChange(existing.get(), updated.build()); stageEntityChange(existing.get(), updated.build());
if (alsoCloudSql) {
cloudSqlReservedList =
google.registry.schema.tld.ReservedList.create(
name, shouldPublish, parseToReservationsByLabels(allLines));
}
}
@Override
void saveToCloudSql() {
jpaTm()
.transact(
() -> {
checkArgument(
ReservedListDao.checkExists(cloudSqlReservedList.getName()),
"A reserved list of this name doesn't exist: %s.",
cloudSqlReservedList.getName());
ReservedListDao.save(cloudSqlReservedList);
});
} }
} }

View file

@ -26,7 +26,10 @@ import google.registry.persistence.UpdateAutoTimestampConverterTest;
import google.registry.persistence.ZonedDateTimeConverterTest; import google.registry.persistence.ZonedDateTimeConverterTest;
import google.registry.schema.cursor.CursorDaoTest; import google.registry.schema.cursor.CursorDaoTest;
import google.registry.schema.tld.PremiumListDaoTest; import google.registry.schema.tld.PremiumListDaoTest;
import google.registry.schema.tld.ReservedListDaoTest;
import google.registry.schema.tmch.ClaimsListDaoTest; import google.registry.schema.tmch.ClaimsListDaoTest;
import google.registry.tools.CreateReservedListCommandTest;
import google.registry.tools.UpdateReservedListCommandTest;
import google.registry.ui.server.registrar.RegistryLockGetActionTest; import google.registry.ui.server.registrar.RegistryLockGetActionTest;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Suite; import org.junit.runners.Suite;
@ -47,6 +50,7 @@ import org.junit.runners.Suite.SuiteClasses;
BloomFilterConverterTest.class, BloomFilterConverterTest.class,
ClaimsListDaoTest.class, ClaimsListDaoTest.class,
CreateAutoTimestampConverterTest.class, CreateAutoTimestampConverterTest.class,
CreateReservedListCommandTest.class,
CurrencyUnitConverterTest.class, CurrencyUnitConverterTest.class,
CursorDaoTest.class, CursorDaoTest.class,
DateTimeConverterTest.class, DateTimeConverterTest.class,
@ -56,7 +60,9 @@ import org.junit.runners.Suite.SuiteClasses;
PremiumListDaoTest.class, PremiumListDaoTest.class,
RegistryLockDaoTest.class, RegistryLockDaoTest.class,
RegistryLockGetActionTest.class, RegistryLockGetActionTest.class,
ReservedListDaoTest.class,
UpdateAutoTimestampConverterTest.class, UpdateAutoTimestampConverterTest.class,
UpdateReservedListCommandTest.class,
ZonedDateTimeConverterTest.class ZonedDateTimeConverterTest.class
}) })
public class SqlIntegrationTestSuite {} public class SqlIntegrationTestSuite {}

View file

@ -0,0 +1,69 @@
// Copyright 2019 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.schema.tld;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import com.google.common.collect.ImmutableMap;
import google.registry.model.registry.label.ReservationType;
import google.registry.model.transaction.JpaTransactionManagerRule;
import google.registry.schema.tld.ReservedList.ReservedEntry;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link ReservedListDao}. */
@RunWith(JUnit4.class)
public class ReservedListDaoTest {
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder().build();
private static final ImmutableMap<String, ReservedEntry> TEST_RESERVATIONS =
ImmutableMap.of(
"food",
ReservedEntry.create(ReservationType.RESERVED_FOR_SPECIFIC_USE, null),
"music",
ReservedEntry.create(ReservationType.FULLY_BLOCKED, "fully blocked"));
@Test
public void save_worksSuccessfully() {
ReservedList reservedList = ReservedList.create("testname", false, TEST_RESERVATIONS);
ReservedListDao.save(reservedList);
jpaTm()
.transact(
() -> {
ReservedList persistedList =
jpaTm()
.getEntityManager()
.createQuery("FROM ReservedList WHERE name = :name", ReservedList.class)
.setParameter("name", "testname")
.getSingleResult();
assertThat(persistedList.getLabelsToReservations())
.containsExactlyEntriesIn(TEST_RESERVATIONS);
assertThat(persistedList.getCreationTimestamp())
.isEqualTo(jpaTmRule.getTxnClock().nowUtc());
});
}
@Test
public void checkExists_worksSuccessfully() {
assertThat(ReservedListDao.checkExists("testlist")).isFalse();
ReservedListDao.save(ReservedList.create("testlist", false, TEST_RESERVATIONS));
assertThat(ReservedListDao.checkExists("testlist")).isTrue();
}
}

View file

@ -15,6 +15,8 @@
package google.registry.schema.tld; package google.registry.schema.tld;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED;
import static google.registry.testing.JUnitBackports.assertThrows;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import google.registry.model.registry.label.ReservationType; import google.registry.model.registry.label.ReservationType;
@ -50,4 +52,34 @@ public class ReservedListTest {
ReservedEntry.create( ReservedEntry.create(
ReservationType.RESERVED_FOR_ANCHOR_TENANT, "reserved for anchor tenant")); ReservationType.RESERVED_FOR_ANCHOR_TENANT, "reserved for anchor tenant"));
} }
@Test
public void create_throwsExceptionWhenLabelIsNotLowercase() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
ReservedList.create(
"UPPER.tld",
true,
ImmutableMap.of("UPPER", ReservedEntry.create(FULLY_BLOCKED, ""))));
assertThat(e)
.hasMessageThat()
.contains("Label(s) [UPPER] must be in puny-coded, lower-case form");
}
@Test
public void create_labelMustBePunyCoded() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
ReservedList.create(
"lower.みんな",
true,
ImmutableMap.of("みんな", ReservedEntry.create(FULLY_BLOCKED, ""))));
assertThat(e)
.hasMessageThat()
.contains("Label(s) [みんな] must be in puny-coded, lower-case form");
}
} }

View file

@ -0,0 +1,126 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.registry.label.ReservationType.ALLOWED_IN_SUNRISE;
import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED;
import static google.registry.testing.JUnitBackports.assertThrows;
import static google.registry.tools.CreateOrUpdateReservedListCommand.parseToReservationsByLabels;
import com.google.common.collect.ImmutableList;
import google.registry.model.registry.label.ReservationType;
import google.registry.schema.tld.ReservedList.ReservedEntry;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link CreateOrUpdateReservedListCommand}. */
@RunWith(JUnit4.class)
public class CreateOrUpdateReservedListCommandTest {
@Test
public void parseToReservationsByLabels_worksCorrectly() {
assertThat(
parseToReservationsByLabels(
ImmutableList.of(
"reserveddomain,FULLY_BLOCKED",
"availableinga,ALLOWED_IN_SUNRISE#allowed_in_sunrise",
"fourletterword,FULLY_BLOCKED")))
.containsExactly(
"reserveddomain",
ReservedEntry.create(ReservationType.FULLY_BLOCKED, ""),
"availableinga",
ReservedEntry.create(ALLOWED_IN_SUNRISE, "allowed_in_sunrise"),
"fourletterword",
ReservedEntry.create(FULLY_BLOCKED, ""));
}
@Test
public void parseToReservationsByLabels_throwsExceptionForInvalidLabel() {
Throwable thrown =
assertThrows(
IllegalArgumentException.class,
() ->
parseToReservationsByLabels(
ImmutableList.of("reserveddomain,FULLY_BLOCKED", "UPPER,FULLY_BLOCKED")));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Label 'UPPER' must be in puny-coded, lower-case form");
}
@Test
public void parseToReservationsByLabels_throwsExceptionForNonPunyCodedLabel() {
Throwable thrown =
assertThrows(
IllegalArgumentException.class,
() ->
parseToReservationsByLabels(
ImmutableList.of("reserveddomain,FULLY_BLOCKED", "みんな,FULLY_BLOCKED")));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Label 'みんな' must be in puny-coded, lower-case form");
}
@Test
public void parseToReservationsByLabels_throwsExceptionForInvalidReservationType() {
Throwable thrown =
assertThrows(
IllegalArgumentException.class,
() ->
parseToReservationsByLabels(
ImmutableList.of(
"reserveddomain,FULLY_BLOCKED", "invalidtype,INVALID_RESERVATION_TYPE")));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"No enum constant"
+ " google.registry.model.registry.label.ReservationType.INVALID_RESERVATION_TYPE");
}
@Test
public void parseToReservationsByLabels_throwsExceptionForInvalidLines() {
Throwable thrown =
assertThrows(
IllegalArgumentException.class,
() ->
parseToReservationsByLabels(
ImmutableList.of(
"reserveddomain,FULLY_BLOCKED,too,many,parts",
"fourletterword,FULLY_BLOCKED")));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Could not parse line in reserved list: reserveddomain,FULLY_BLOCKED,too,many,parts");
}
@Test
public void parseToReservationsByLabels_throwsExceptionForDuplicateEntries() {
Throwable thrown =
assertThrows(
IllegalStateException.class,
() ->
parseToReservationsByLabels(
ImmutableList.of(
"reserveddomain,FULLY_BLOCKED",
"fourletterword,FULLY_BLOCKED",
"fourletterword,FULLY_BLOCKED")));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Reserved list cannot contain duplicate labels. Dupes (with counts) were:"
+ " [fourletterword x 2]");
}
}

View file

@ -14,15 +14,26 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.JUnitBackports.assertThrows; import static google.registry.testing.JUnitBackports.assertThrows;
import static google.registry.testing.TestDataHelper.loadFile; import static google.registry.testing.TestDataHelper.loadFile;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import com.beust.jcommander.ParameterException; import com.beust.jcommander.ParameterException;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.google.common.truth.Truth8;
import google.registry.model.registry.label.ReservedList;
import google.registry.model.transaction.JpaTransactionManagerRule;
import google.registry.schema.tld.ReservedList.ReservedEntry;
import google.registry.schema.tld.ReservedListDao;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
import javax.persistence.EntityManager;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
/** /**
@ -33,6 +44,10 @@ import org.junit.Test;
public abstract class CreateOrUpdateReservedListCommandTestCase public abstract class CreateOrUpdateReservedListCommandTestCase
<T extends CreateOrUpdateReservedListCommand> extends CommandTestCase<T> { <T extends CreateOrUpdateReservedListCommand> extends CommandTestCase<T> {
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder().build();
String reservedTermsPath; String reservedTermsPath;
String invalidReservedTermsPath; String invalidReservedTermsPath;
@ -64,4 +79,52 @@ public abstract class CreateOrUpdateReservedListCommandTestCase
IllegalArgumentException.class, IllegalArgumentException.class,
() -> runCommandForced("--name=xn--q9jyb4c-blork", "--input=" + invalidReservedTermsPath)); () -> runCommandForced("--name=xn--q9jyb4c-blork", "--input=" + invalidReservedTermsPath));
} }
google.registry.schema.tld.ReservedList createCloudSqlReservedList(
String name, boolean shouldPublish, Map<String, ReservedEntry> labelsToEntries) {
return google.registry.schema.tld.ReservedList.create(name, shouldPublish, labelsToEntries);
}
google.registry.schema.tld.ReservedList getCloudSqlReservedList(String name) {
return jpaTm()
.transact(
() -> {
EntityManager em = jpaTm().getEntityManager();
long revisionId =
em.createQuery(
"SELECT MAX(rl.revisionId) FROM ReservedList rl WHERE name = :name",
Long.class)
.setParameter("name", name)
.getSingleResult();
return em.createQuery(
"FROM ReservedList rl LEFT JOIN FETCH rl.labelsToReservations WHERE"
+ " rl.revisionId = :revisionId",
google.registry.schema.tld.ReservedList.class)
.setParameter("revisionId", revisionId)
.getSingleResult();
});
}
void verifyXnq9jyb4cInCloudSql() {
assertThat(ReservedListDao.checkExists("xn--q9jyb4c_common-reserved")).isTrue();
google.registry.schema.tld.ReservedList persistedList =
getCloudSqlReservedList("xn--q9jyb4c_common-reserved");
assertThat(persistedList.getName()).isEqualTo("xn--q9jyb4c_common-reserved");
assertThat(persistedList.getShouldPublish()).isTrue();
assertThat(persistedList.getCreationTimestamp()).isEqualTo(jpaTmRule.getTxnClock().nowUtc());
assertThat(persistedList.getLabelsToReservations())
.containsExactly(
"baddies",
ReservedEntry.create(FULLY_BLOCKED, ""),
"ford",
ReservedEntry.create(FULLY_BLOCKED, "random comment"));
}
void verifyXnq9jyb4cInDatastore() {
Truth8.assertThat(ReservedList.get("xn--q9jyb4c_common-reserved")).isPresent();
ReservedList reservedList = ReservedList.get("xn--q9jyb4c_common-reserved").get();
assertThat(reservedList.getReservedListEntries()).hasSize(2);
Truth8.assertThat(reservedList.getReservationInList("baddies")).hasValue(FULLY_BLOCKED);
Truth8.assertThat(reservedList.getReservationInList("ford")).hasValue(FULLY_BLOCKED);
}
} }

View file

@ -24,8 +24,11 @@ import static google.registry.testing.JUnitBackports.assertThrows;
import static google.registry.tools.CreateReservedListCommand.INVALID_FORMAT_ERROR_MESSAGE; import static google.registry.tools.CreateReservedListCommand.INVALID_FORMAT_ERROR_MESSAGE;
import static org.joda.time.DateTimeZone.UTC; import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableMap;
import google.registry.model.registry.Registry; import google.registry.model.registry.Registry;
import google.registry.model.registry.label.ReservedList; import google.registry.model.registry.label.ReservedList;
import google.registry.schema.tld.ReservedList.ReservedEntry;
import google.registry.schema.tld.ReservedListDao;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -173,6 +176,28 @@ public class CreateReservedListCommandTest extends
runNameTestExpectedFailure("soy_$oy", INVALID_FORMAT_ERROR_MESSAGE); runNameTestExpectedFailure("soy_$oy", INVALID_FORMAT_ERROR_MESSAGE);
} }
@Test
public void testSaveToCloudSql_succeeds() throws Exception {
runCommandForced(
"--name=xn--q9jyb4c_common-reserved", "--input=" + reservedTermsPath, "--also_cloud_sql");
verifyXnq9jyb4cInDatastore();
verifyXnq9jyb4cInCloudSql();
}
@Test
public void testSaveToCloudSql_noExceptionThrownWhenSaveFail() throws Exception {
// Note that, during the dual-write phase, we want to make sure that no exception will be
// thrown if saving reserved list to Cloud SQL fails.
ReservedListDao.save(
createCloudSqlReservedList(
"xn--q9jyb4c_common-reserved",
true,
ImmutableMap.of("testdomain", ReservedEntry.create(FULLY_BLOCKED, ""))));
runCommandForced(
"--name=xn--q9jyb4c_common-reserved", "--input=" + reservedTermsPath, "--also_cloud_sql");
verifyXnq9jyb4cInDatastore();
}
private void runNameTestExpectedFailure(String name, String expectedErrorMsg) { private void runNameTestExpectedFailure(String name, String expectedErrorMsg) {
IllegalArgumentException thrown = IllegalArgumentException thrown =
assertThrows( assertThrows(

View file

@ -22,14 +22,17 @@ import static google.registry.testing.JUnitBackports.assertThrows;
import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.model.registry.label.ReservedList; import google.registry.model.registry.label.ReservedList;
import google.registry.schema.tld.ReservedList.ReservedEntry;
import google.registry.schema.tld.ReservedListDao;
import org.junit.Test; import org.junit.Test;
/** Unit tests for {@link UpdateReservedListCommand}. */ /** Unit tests for {@link UpdateReservedListCommand}. */
public class UpdateReservedListCommandTest extends public class UpdateReservedListCommandTest extends
CreateOrUpdateReservedListCommandTestCase<UpdateReservedListCommand> { CreateOrUpdateReservedListCommandTestCase<UpdateReservedListCommand> {
private void populateInitialReservedList(boolean shouldPublish) { private void populateInitialReservedListInDatastore(boolean shouldPublish) {
persistResource( persistResource(
new ReservedList.Builder() new ReservedList.Builder()
.setName("xn--q9jyb4c_common-reserved") .setName("xn--q9jyb4c_common-reserved")
@ -40,6 +43,14 @@ public class UpdateReservedListCommandTest extends
.build()); .build());
} }
private void populateInitialReservedListInCloudSql(boolean shouldPublish) {
ReservedListDao.save(
createCloudSqlReservedList(
"xn--q9jyb4c_common-reserved",
shouldPublish,
ImmutableMap.of("helicopter", ReservedEntry.create(FULLY_BLOCKED, ""))));
}
@Test @Test
public void testSuccess() throws Exception { public void testSuccess() throws Exception {
runSuccessfulUpdateTest("--name=xn--q9jyb4c_common-reserved", "--input=" + reservedTermsPath); runSuccessfulUpdateTest("--name=xn--q9jyb4c_common-reserved", "--input=" + reservedTermsPath);
@ -52,7 +63,7 @@ public class UpdateReservedListCommandTest extends
@Test @Test
public void testSuccess_lastUpdateTime_updatedCorrectly() throws Exception { public void testSuccess_lastUpdateTime_updatedCorrectly() throws Exception {
populateInitialReservedList(true); populateInitialReservedListInDatastore(true);
ReservedList original = ReservedList.get("xn--q9jyb4c_common-reserved").get(); ReservedList original = ReservedList.get("xn--q9jyb4c_common-reserved").get();
runCommandForced("--input=" + reservedTermsPath); runCommandForced("--input=" + reservedTermsPath);
ReservedList updated = ReservedList.get("xn--q9jyb4c_common-reserved").get(); ReservedList updated = ReservedList.get("xn--q9jyb4c_common-reserved").get();
@ -71,7 +82,7 @@ public class UpdateReservedListCommandTest extends
@Test @Test
public void testSuccess_shouldPublish_doesntOverrideFalseIfNotSpecified() throws Exception { public void testSuccess_shouldPublish_doesntOverrideFalseIfNotSpecified() throws Exception {
populateInitialReservedList(false); populateInitialReservedListInDatastore(false);
runCommandForced("--input=" + reservedTermsPath); runCommandForced("--input=" + reservedTermsPath);
assertThat(ReservedList.get("xn--q9jyb4c_common-reserved")).isPresent(); assertThat(ReservedList.get("xn--q9jyb4c_common-reserved")).isPresent();
ReservedList reservedList = ReservedList.get("xn--q9jyb4c_common-reserved").get(); ReservedList reservedList = ReservedList.get("xn--q9jyb4c_common-reserved").get();
@ -79,7 +90,7 @@ public class UpdateReservedListCommandTest extends
} }
private void runSuccessfulUpdateTest(String... args) throws Exception { private void runSuccessfulUpdateTest(String... args) throws Exception {
populateInitialReservedList(true); populateInitialReservedListInDatastore(true);
runCommandForced(args); runCommandForced(args);
assertThat(ReservedList.get("xn--q9jyb4c_common-reserved")).isPresent(); assertThat(ReservedList.get("xn--q9jyb4c_common-reserved")).isPresent();
ReservedList reservedList = ReservedList.get("xn--q9jyb4c_common-reserved").get(); ReservedList reservedList = ReservedList.get("xn--q9jyb4c_common-reserved").get();
@ -100,4 +111,25 @@ public class UpdateReservedListCommandTest extends
runCommand("--force", "--name=xn--q9jyb4c_poobah", "--input=" + reservedTermsPath)); runCommand("--force", "--name=xn--q9jyb4c_poobah", "--input=" + reservedTermsPath));
assertThat(thrown).hasMessageThat().contains(errorMessage); assertThat(thrown).hasMessageThat().contains(errorMessage);
} }
@Test
public void testSaveToCloudSql_succeeds() throws Exception {
populateInitialReservedListInDatastore(true);
populateInitialReservedListInCloudSql(true);
runCommandForced(
"--name=xn--q9jyb4c_common-reserved", "--input=" + reservedTermsPath, "--also_cloud_sql");
verifyXnq9jyb4cInDatastore();
verifyXnq9jyb4cInCloudSql();
}
@Test
public void testSaveToCloudSql_noExceptionThrownWhenSaveFail() throws Exception {
// Note that, during the dual-write phase, we want to make sure that no exception will be
// thrown if saving reserved list to Cloud SQL fails.
populateInitialReservedListInDatastore(true);
runCommandForced(
"--name=xn--q9jyb4c_common-reserved", "--input=" + reservedTermsPath, "--also_cloud_sql");
verifyXnq9jyb4cInDatastore();
assertThat(ReservedListDao.checkExists("xn--q9jyb4c_common-reserved")).isFalse();
}
} }