Change premium list command to be based off of mutating command (#1123)

* Change premium list command to be based off of mutating command

* Modify test cases and add comments for better readability

* Fix typo
This commit is contained in:
Rachel Guan 2021-05-14 08:40:03 -04:00 committed by GitHub
parent 7b03d28ab1
commit 0f4bf7ac38
6 changed files with 401 additions and 259 deletions

View file

@ -14,39 +14,28 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.security.JsonHttp.JSON_SAFETY_PREFIX;
import static google.registry.tools.server.CreateOrUpdatePremiumListAction.INPUT_PARAM;
import static google.registry.tools.server.CreateOrUpdatePremiumListAction.NAME_PARAM;
import static google.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.google.common.base.Joiner; import com.google.common.flogger.FluentLogger;
import com.google.common.base.Verify; import google.registry.schema.tld.PremiumListSqlDao;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.MediaType;
import google.registry.model.registry.label.PremiumList;
import google.registry.tools.params.PathParameter; import google.registry.tools.params.PathParameter;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.json.simple.JSONValue;
/** /**
* Base class for specification of command line parameters common to creating and updating premium * Base class for specification of command line parameters common to creating and updating premium
* lists. * lists.
*/ */
abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand abstract class CreateOrUpdatePremiumListCommand extends MutatingCommand {
implements CommandWithConnection, CommandWithRemoteApi {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
protected List<String> inputData;
@Nullable @Nullable
@Parameter( @Parameter(
names = {"-n", "--name"}, names = {"-n", "--name"},
description = "The name of this premium list (defaults to filename if not specified). " description =
"The name of this premium list (defaults to filename if not specified). "
+ "This is almost always the name of the TLD this premium list will be used on.") + "This is almost always the name of the TLD this premium list will be used on.")
String name; String name;
@ -57,78 +46,17 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
required = true) required = true)
Path inputFile; Path inputFile;
protected AppEngineConnection connection;
protected int inputLineCount;
@Override
public void setConnection(AppEngineConnection connection) {
this.connection = connection;
}
abstract String getCommandPath();
ImmutableMap<String, String> getParameterMap() {
return ImmutableMap.of();
}
@Override
protected void init() throws Exception {
name = isNullOrEmpty(name) ? convertFilePathToName(inputFile) : name;
List<String> lines = Files.readAllLines(inputFile, UTF_8);
// Try constructing and parsing the premium list locally to check up front for validation errors
new PremiumList.Builder().setName(name).build().parse(lines);
inputLineCount = lines.size();
}
@Override
protected String prompt() {
return String.format(
"You are about to save the premium list %s with %d items: ", name, inputLineCount);
}
@Override @Override
public String execute() throws Exception { public String execute() throws Exception {
ImmutableMap.Builder<String, String> params = new ImmutableMap.Builder<>(); String message = String.format("Saved premium list %s with %d entries", name, inputData.size());
params.put(NAME_PARAM, name); try {
String inputFileContents = new String(Files.readAllBytes(inputFile), UTF_8); logger.atInfo().log("Saving premium list for TLD %s", name);
String requestBody = PremiumListSqlDao.save(name, inputData);
Joiner.on('&').withKeyValueSeparator("=").join( logger.atInfo().log(message);
ImmutableMap.of(INPUT_PARAM, URLEncoder.encode(inputFileContents, UTF_8.toString()))); } catch (Throwable e) {
message = "Unexpected error saving premium list from nomulus tool command";
ImmutableMap<String, String> extraParams = getParameterMap(); logger.atSevere().withCause(e).log(message);
if (extraParams != null) {
params.putAll(extraParams);
} }
return message;
// Call the server and get the response data
String response =
connection.sendPostRequest(
getCommandPath(), params.build(), MediaType.FORM_DATA, requestBody.getBytes(UTF_8));
return extractServerResponse(response);
}
// TODO(user): refactor this behavior into a better general-purpose
// response validation that can be re-used across the new client/server commands.
private String extractServerResponse(String response) {
Map<String, Object> responseMap = toMap(JSONValue.parse(stripJsonPrefix(response)));
// TODO(user): consider using jart's FormField Framework.
// See: j/c/g/d/r/ui/server/RegistrarFormFields.java
String status = (String) responseMap.get("status");
Verify.verify(!status.equals("error"), "Server error: %s", responseMap.get("error"));
return String.format("Successfully saved premium list %s\n", name);
}
@SuppressWarnings("unchecked")
static Map<String, Object> toMap(Object obj) {
Verify.verify(obj instanceof Map<?, ?>, "JSON object is not a Map: %s", obj);
return (Map<String, Object>) obj;
}
// TODO(user): figure out better place to put this method to make it re-usable
private static String stripJsonPrefix(String json) {
Verify.verify(json.startsWith(JSON_SAFETY_PREFIX));
return json.substring(JSON_SAFETY_PREFIX.length());
} }
} }

View file

@ -14,14 +14,23 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.registry.Registries.assertTldExists;
import static google.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableMap; import com.google.common.base.Strings;
import com.googlecode.objectify.Key;
import google.registry.model.registry.label.PremiumList; import google.registry.model.registry.label.PremiumList;
import google.registry.tools.server.CreatePremiumListAction; import google.registry.persistence.VKey;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.schema.tld.PremiumListUtils;
import java.nio.file.Files;
/** Command to create a {@link PremiumList} on Datastore. */ /** Command to create a {@link PremiumList} on Database. */
@Parameters(separators = " =", commandDescription = "Create a PremiumList in Datastore.") @Parameters(separators = " =", commandDescription = "Create a PremiumList in Database.")
public class CreatePremiumListCommand extends CreateOrUpdatePremiumListCommand { public class CreatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
@Parameter( @Parameter(
@ -29,18 +38,24 @@ public class CreatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
description = "Override restrictions on premium list naming") description = "Override restrictions on premium list naming")
boolean override; boolean override;
/** Returns the path to the servlet task. */
@Override @Override
public String getCommandPath() { // Using CreatePremiumListAction.java as reference;
return CreatePremiumListAction.PATH; protected void init() throws Exception {
} name = Strings.isNullOrEmpty(name) ? convertFilePathToName(inputFile) : name;
checkArgument(
@Override !PremiumListSqlDao.getLatestRevision(name).isPresent(),
ImmutableMap<String, String> getParameterMap() { "A premium list already exists by this name");
if (override) { if (!override) {
return ImmutableMap.of("override", "true"); // refer to CreatePremiumListAction.java
} else { assertTldExists(
return ImmutableMap.of(); name,
"Premium names must match the name of the TLD they are intended to be used on"
+ " (unless --override is specified), yet TLD %s does not exist");
} }
inputData = Files.readAllLines(inputFile, UTF_8);
// create a premium list with only input data and store as the first version of the entity
PremiumList newPremiumList = PremiumListUtils.parseToPremiumList(name, inputData);
stageEntityChange(
null, newPremiumList, VKey.createOfy(PremiumList.class, Key.create(newPremiumList)));
} }
} }

View file

@ -14,18 +14,86 @@
package google.registry.tools; package google.registry.tools;
import com.beust.jcommander.Parameters; import static com.google.common.base.Preconditions.checkArgument;
import google.registry.model.registry.label.PremiumList; import static com.google.common.collect.ImmutableSet.toImmutableSet;
import google.registry.tools.server.UpdatePremiumListAction; import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8;
/** Command to safely update {@link PremiumList} in Datastore for a given TLD. */ import com.beust.jcommander.Parameters;
@Parameters(separators = " =", commandDescription = "Update a PremiumList in Datastore.") import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.googlecode.objectify.Key;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.persistence.VKey;
import google.registry.schema.tld.PremiumEntry;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.schema.tld.PremiumListUtils;
import java.nio.file.Files;
import java.util.List;
import java.util.Optional;
import org.joda.money.BigMoney;
/** Command to safely update {@link PremiumList} in Database for a given TLD. */
@Parameters(separators = " =", commandDescription = "Update a PremiumList in Database.")
class UpdatePremiumListCommand extends CreateOrUpdatePremiumListCommand { class UpdatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
/** Returns the path to the servlet task. */
@Override @Override
public String getCommandPath() { // Using UpdatePremiumListAction.java as reference;
return UpdatePremiumListAction.PATH; protected void init() throws Exception {
name = Strings.isNullOrEmpty(name) ? convertFilePathToName(inputFile) : name;
List<String> existingEntry = getExistingPremiumListEntry(name).asList();
inputData = Files.readAllLines(inputFile, UTF_8);
// reconstructing existing premium list to bypass Hibernate lazy initialization exception
PremiumList existingPremiumList = PremiumListUtils.parseToPremiumList(name, existingEntry);
PremiumList updatedPremiumList = PremiumListUtils.parseToPremiumList(name, inputData);
// use LabelsToPrices() for comparison between old and new premium lists since they have
// different creation date, updated date even if they have same content;
if (!existingPremiumList.getLabelsToPrices().equals(updatedPremiumList.getLabelsToPrices())) {
stageEntityChange(
existingPremiumList,
updatedPremiumList,
VKey.createOfy(PremiumList.class, Key.create(existingPremiumList)));
}
}
/*
To get premium list content as a set of string. This is a workaround to avoid dealing with
Hibernate.LazyInitizationException error. It occurs when trying to access data of the
latest revision of an existing premium list.
"Cannot evaluate google.registry.model.registry.label.PremiumList.toString()'".
Ideally, the following should be the way to verify info in latest revision of a premium list:
PremiumList existingPremiumList =
PremiumListSqlDao.getLatestRevision(name)
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Could not update premium list %s because it doesn't exist.", name)));
assertThat(persistedList.getLabelsToPrices()).containsEntry("foo", new BigDecimal("9000.00"));
assertThat(persistedList.size()).isEqualTo(1);
*/
protected ImmutableSet<String> getExistingPremiumListEntry(String name) {
Optional<PremiumList> list = PremiumListSqlDao.getLatestRevision(name);
checkArgument(
list.isPresent(),
String.format("Could not update premium list %s because it doesn't exist.", name));
Iterable<PremiumEntry> sqlListEntries =
jpaTm().transact(() -> PremiumListSqlDao.loadPremiumListEntriesUncached(list.get()));
return Streams.stream(sqlListEntries)
.map(
premiumEntry ->
new PremiumListEntry.Builder()
.setPrice(
BigMoney.of(list.get().getCurrency(), premiumEntry.getPrice()).toMoney())
.setLabel(premiumEntry.getDomainLabel())
.build()
.toString())
.collect(toImmutableSet());
} }
} }

View file

@ -14,47 +14,64 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.truth.Truth.assertThat; import static google.registry.model.registry.Registry.TldState.GENERAL_AVAILABILITY;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.newRegistry;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableMap; import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.google.common.net.MediaType; import google.registry.dns.writer.VoidDnsWriter;
import google.registry.testing.UriParameters; import google.registry.model.pricing.StaticPremiumListPricingEngine;
import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldType;
import java.io.File; import java.io.File;
import java.nio.charset.StandardCharsets; import java.io.IOException;
import org.mockito.ArgumentCaptor; import org.junit.jupiter.api.BeforeEach;
import org.mockito.Captor;
/** Base class for common testing setup for create and update commands for Premium Lists. */ /** Base class for common testing setup for create and update commands for Premium Lists. */
abstract class CreateOrUpdatePremiumListCommandTestCase<T extends CreateOrUpdatePremiumListCommand> abstract class CreateOrUpdatePremiumListCommandTestCase<T extends CreateOrUpdatePremiumListCommand>
extends CommandTestCase<T> { extends CommandTestCase<T> {
@Captor protected static final String TLD_TEST = "prime";
ArgumentCaptor<ImmutableMap<String, String>> urlParamCaptor; protected String premiumTermsPath;
protected String initialPremiumListData;
@Captor @BeforeEach
ArgumentCaptor<byte[]> requestBodyCaptor; void beforeEachCreateOrUpdatePremiumListCommandTestCase() throws IOException {
// initial set up for both CreatePremiumListCommand and UpdatePremiumListCommand test cases;
static String generateInputData(String premiumTermsPath) throws Exception { initialPremiumListData = "doge,USD 9090";
return Files.asCharSource(new File(premiumTermsPath), StandardCharsets.UTF_8).read(); File premiumTermsFile = tmpDir.resolve(TLD_TEST + ".txt").toFile();
Files.asCharSink(premiumTermsFile, UTF_8).write(initialPremiumListData);
premiumTermsPath = premiumTermsFile.getPath();
} }
void verifySentParams( Registry createRegistry(String tldStr, String premiumListInput) {
AppEngineConnection connection, String path, ImmutableMap<String, String> parameterMap) Registry registry;
throws Exception { if (premiumListInput != null) {
verify(connection) registry =
.sendPostRequest( newRegistry(
eq(path), tldStr,
urlParamCaptor.capture(), Ascii.toUpperCase(tldStr),
eq(MediaType.FORM_DATA), ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY),
requestBodyCaptor.capture()); TldType.TEST);
assertThat(new ImmutableMap.Builder<String, String>() persistPremiumList(tldStr, premiumListInput);
.putAll(urlParamCaptor.getValue()) persistResource(registry);
.putAll(UriParameters.parse(new String(requestBodyCaptor.getValue(), UTF_8)).entries()) } else {
.build()) registry =
.containsExactlyEntriesIn(parameterMap); new Registry.Builder()
.setTldStr(tldStr)
.setPremiumPricingEngine(StaticPremiumListPricingEngine.NAME)
.setDnsWriters(ImmutableSet.of(VoidDnsWriter.NAME))
.setPremiumList(null)
.build();
tm().transact(() -> tm().put(registry));
}
return registry;
} }
} }

View file

@ -15,105 +15,125 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.JsonResponse.JSON_SAFETY_PREFIX; import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.TestDataHelper.loadFile;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import com.beust.jcommander.ParameterException; import com.google.common.io.Files;
import com.google.common.base.VerifyException; import google.registry.model.registry.Registry;
import com.google.common.collect.ImmutableMap; import google.registry.schema.tld.PremiumListSqlDao;
import com.google.common.net.MediaType; import java.nio.file.Path;
import google.registry.tools.server.CreatePremiumListAction; import java.nio.file.Paths;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
/** Unit tests for {@link CreatePremiumListCommand}. */ /** Unit tests for {@link CreatePremiumListCommand}. */
class CreatePremiumListCommandTest<C extends CreatePremiumListCommand> class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
extends CreateOrUpdatePremiumListCommandTestCase<C> { extends CreateOrUpdatePremiumListCommandTestCase<C> {
Registry registry;
@Mock AppEngineConnection connection;
private String premiumTermsPath;
String premiumTermsCsv;
private String servletPath;
@BeforeEach @BeforeEach
void beforeEach() throws Exception { void beforeEach() {
command.setConnection(connection); registry = createRegistry(TLD_TEST, null);
premiumTermsPath =
writeToNamedTmpFile(
"example_premium_terms.csv",
loadFile(CreatePremiumListCommandTest.class, "example_premium_terms.csv"));
servletPath = "/_dr/admin/createPremiumList";
when(connection.sendPostRequest(
eq(CreatePremiumListAction.PATH),
ArgumentMatchers.<String, String>anyMap(),
any(MediaType.class),
any(byte[].class)))
.thenReturn(JSON_SAFETY_PREFIX + "{\"status\":\"success\",\"lines\":[]}");
} }
@Test @Test
void testRun() throws Exception { void verify_registryIsSetUpCorrectly() {
runCommandForced("-i=" + premiumTermsPath, "-n=foo"); // ensure that no premium list is created before running the command
assertInStdout("Successfully"); // this check also implicitly verifies the TLD is successfully created;
verifySentParams( assertThat(PremiumListSqlDao.getLatestRevision(TLD_TEST).isPresent()).isFalse();
connection,
servletPath,
ImmutableMap.of("name", "foo", "inputData", generateInputData(premiumTermsPath)));
} }
@Test @Test
void testRun_noProvidedName_usesBasenameOfInputFile() throws Exception { void commandRun_successCreateList() throws Exception {
runCommandForced("-i=" + premiumTermsPath); runCommandForced("--name=" + TLD_TEST, "--input=" + premiumTermsPath);
assertInStdout("Successfully"); assertThat(registry.getTld().toString()).isEqualTo(TLD_TEST);
verifySentParams( assertThat(PremiumListSqlDao.getLatestRevision(TLD_TEST).isPresent()).isTrue();
connection,
servletPath,
ImmutableMap.of(
"name", "example_premium_terms", "inputData", generateInputData(premiumTermsPath)));
} }
@Test @Test
void testRun_errorResponse() throws Exception { // since the old entity is always null and file cannot be empty, the prompt will NOT be "No entity
reset(connection); // changes to apply."
command.setConnection(connection); void commandInit_successStageNewEntity() throws Exception {
when(connection.sendPostRequest( CreatePremiumListCommand command = new CreatePremiumListCommand();
eq(CreatePremiumListAction.PATH), anyMap(), any(MediaType.class), any(byte[].class))) command.inputFile = Paths.get(premiumTermsPath);
.thenReturn(JSON_SAFETY_PREFIX + "{\"status\":\"error\",\"error\":\"foo already exists\"}"); command.init();
VerifyException thrown = assertThat(command.prompt()).contains("Create PremiumList@");
assertThrows( assertThat(command.prompt()).contains(String.format("name=%s", TLD_TEST));
VerifyException.class, () -> runCommandForced("-i=" + premiumTermsPath, "-n=foo"));
assertThat(thrown).hasMessageThat().contains("Server error:");
} }
@Test @Test
@MockitoSettings(strictness = Strictness.LENIENT) void commandInit_successStageNewEntityWithOverride() throws Exception {
void testRun_noInputFileSpecified_throwsException() { CreatePremiumListCommand command = new CreatePremiumListCommand();
ParameterException thrown = assertThrows(ParameterException.class, this::runCommand); String alterTld = "override";
assertThat(thrown).hasMessageThat().contains("The following option is required"); command.inputFile = Paths.get(premiumTermsPath);
command.override = true;
command.name = alterTld;
command.init();
assertThat(command.prompt()).contains("Create PremiumList@");
assertThat(command.prompt()).contains(String.format("name=%s", alterTld));
} }
@Test @Test
@MockitoSettings(strictness = Strictness.LENIENT) void commandInit_failureNoInputFile() {
void testRun_invalidInputData() throws Exception { CreatePremiumListCommand command = new CreatePremiumListCommand();
premiumTermsPath = assertThrows(NullPointerException.class, command::init);
writeToNamedTmpFile( }
"tmp_file2",
loadFile(CreatePremiumListCommandTest.class, "example_invalid_premium_terms.csv")); @Test
IllegalArgumentException thrown = void commandInit_failurePremiumListAlreadyExists() {
assertThrows( String randomStr = "random";
IllegalArgumentException.class, createTld(randomStr);
() -> runCommandForced("-i=" + premiumTermsPath, "-n=foo")); CreatePremiumListCommand command = new CreatePremiumListCommand();
assertThat(thrown).hasMessageThat().contains("Could not parse line in premium list"); command.name = randomStr;
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::init);
assertThat(thrown).hasMessageThat().isEqualTo("A premium list already exists by this name");
}
@Test
void commandInit_failureMismatchedTldFileName_noOverride() throws Exception {
CreatePremiumListCommand command = new CreatePremiumListCommand();
String fileName = "random";
Path tmpPath = tmpDir.resolve(String.format("%s.txt", fileName));
Files.write(new byte[0], tmpPath.toFile());
command.inputFile = tmpPath;
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::init);
assertThat(thrown)
.hasMessageThat()
.contains(
String.format(
"Premium names must match the name of the TLD they are "
+ "intended to be used on (unless --override is specified), "
+ "yet TLD %s does not exist",
fileName));
}
@Test
void commandInit_failureMismatchedTldName_noOverride() {
CreatePremiumListCommand command = new CreatePremiumListCommand();
String fileName = "random";
command.name = fileName;
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::init);
assertThat(thrown)
.hasMessageThat()
.contains(
String.format(
"Premium names must match the name of the TLD they are "
+ "intended to be used on (unless --override is specified), "
+ "yet TLD %s does not exist",
fileName));
}
@Test
void commandInit_failureUseEmptyFile() throws Exception {
CreatePremiumListCommand command = new CreatePremiumListCommand();
String fileName = "empty";
Path tmpPath = tmpDir.resolve(String.format("%s.txt", fileName));
Files.write(new byte[0], tmpPath.toFile());
command.inputFile = tmpPath;
command.name = TLD_TEST;
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::init);
assertThat(thrown)
.hasMessageThat()
.contains("The Cloud SQL schema requires exactly one currency");
} }
} }

View file

@ -14,60 +14,154 @@
package google.registry.tools; package google.registry.tools;
import static google.registry.request.JsonResponse.JSON_SAFETY_PREFIX; import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.TestDataHelper.loadFile; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.ArgumentMatchers.any; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet;
import com.google.common.net.MediaType; import com.google.common.io.Files;
import google.registry.tools.server.UpdatePremiumListAction; import google.registry.model.registry.Registry;
import google.registry.schema.tld.PremiumListSqlDao;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mock;
/** Unit tests for {@link UpdatePremiumListCommand}. */ /** Unit tests for {@link UpdatePremiumListCommand}. */
class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand> class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
extends CreateOrUpdatePremiumListCommandTestCase<C> { extends CreateOrUpdatePremiumListCommandTestCase<C> {
Registry registry;
@Mock AppEngineConnection connection;
private String premiumTermsPath;
String premiumTermsCsv;
private String servletPath;
@BeforeEach @BeforeEach
void beforeEach() throws Exception { void beforeEach() {
command.setConnection(connection); registry = createRegistry(TLD_TEST, initialPremiumListData);
servletPath = "/_dr/admin/updatePremiumList";
premiumTermsPath =
writeToNamedTmpFile(
"example_premium_terms.csv",
loadFile(UpdatePremiumListCommandTest.class, "example_premium_terms.csv"));
when(connection.sendPostRequest(
eq(UpdatePremiumListAction.PATH), anyMap(), any(MediaType.class), any(byte[].class)))
.thenReturn(JSON_SAFETY_PREFIX + "{\"status\":\"success\",\"lines\":[]}");
} }
@Test @Test
void testRun() throws Exception { void verify_registryIsSetUpCorrectly() {
runCommandForced("-i=" + premiumTermsPath, "-n=foo"); // ensure that no premium list is created before running the command
verifySentParams( assertThat(PremiumListSqlDao.getLatestRevision(TLD_TEST).isPresent()).isTrue();
connection, // ensure that there's value in existing premium list;
servletPath, UpdatePremiumListCommand command = new UpdatePremiumListCommand();
ImmutableMap.of("name", "foo", "inputData", generateInputData(premiumTermsPath))); ImmutableSet<String> entries = command.getExistingPremiumListEntry(TLD_TEST);
assertThat(entries.size()).isEqualTo(1);
// data from @beforeEach of CreateOrUpdatePremiumListCommandTestCase.java
assertThat(entries.contains("doge,USD 9090.00")).isTrue();
} }
@Test @Test
void testRun_noProvidedName_usesBasenameOfInputFile() throws Exception { void commandInit_successStageNoEntityChange() throws Exception {
runCommandForced("-i=" + premiumTermsPath); UpdatePremiumListCommand command = new UpdatePremiumListCommand();
assertInStdout("Successfully"); command.inputFile = Paths.get(premiumTermsPath);
verifySentParams( command.name = TLD_TEST;
connection, command.init();
servletPath, assertThat(command.prompt()).contains("No entity changes to apply.");
ImmutableMap.of( }
"name", "example_premium_terms", "inputData", generateInputData(premiumTermsPath)));
@Test
void commandInit_successStageEntityChange() throws Exception {
File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
String newPremiumListData = "omg,JPY 1234";
Files.asCharSink(tmpFile, UTF_8).write(newPremiumListData);
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = Paths.get(tmpFile.getPath());
command.name = TLD_TEST;
command.init();
assertThat(command.prompt()).contains("Update PremiumList@");
}
@Test
void commandRun_successUpdateList() throws Exception {
File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
String newPremiumListData = "eth,USD 9999";
Files.asCharSink(tmpFile, UTF_8).write(newPremiumListData);
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
// data come from @beforeEach of CreateOrUpdatePremiumListCommandTestCase.java
command.inputFile = Paths.get(tmpFile.getPath());
runCommandForced("--name=" + TLD_TEST, "--input=" + command.inputFile);
ImmutableSet<String> entries = command.getExistingPremiumListEntry(TLD_TEST);
assertThat(entries.size()).isEqualTo(1);
// verify that list is updated; cannot use only string since price is formatted;
assertThat(entries.contains("eth,USD 9999.00")).isTrue();
}
@Test
void commandRun_successUpdateMultiLineList() throws Exception {
File tmpFile = tmpDir.resolve(TLD_TEST + ".txt").toFile();
String premiumTerms = "foo,USD 9000\ndoge,USD 100\nelon,USD 2021";
Files.asCharSink(tmpFile, UTF_8).write(premiumTerms);
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = Paths.get(tmpFile.getPath());
runCommandForced("--name=" + TLD_TEST, "--input=" + command.inputFile);
// assert all three lines from premiumTerms are added
ImmutableSet<String> entries = command.getExistingPremiumListEntry(TLD_TEST);
assertThat(entries.size()).isEqualTo(3);
assertThat(entries.contains("foo,USD 9000.00")).isTrue();
assertThat(entries.contains("doge,USD 100.00")).isTrue();
assertThat(entries.contains("elon,USD 2021.00")).isTrue();
}
@Test
void commandInit_failureUpdateEmptyList() throws Exception {
Path tmpPath = tmpDir.resolve(String.format("%s.txt", TLD_TEST));
Files.write(new byte[0], tmpPath.toFile());
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.inputFile = tmpPath;
command.name = TLD_TEST;
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::init);
assertThat(thrown)
.hasMessageThat()
.contains("The Cloud SQL schema requires exactly one currency");
}
@Test
void commandInit_failureNoPreviousVersion() {
String fileName = "random";
registry = createRegistry(fileName, null);
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.name = fileName;
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::init);
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
String.format("Could not update premium list %s because it doesn't exist.", fileName));
}
@Test
void commandInit_failureNoInputFile() {
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
assertThrows(NullPointerException.class, command::init);
}
@Test
void commandInit_failureTldFromNameDoesNotExist() {
String fileName = "random";
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
command.name = fileName;
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::init);
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
String.format("Could not update premium list %s because it doesn't exist.", fileName));
}
@Test
void commandInit_failureTldFromInputFileDoesNotExist() {
String fileName = "random";
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
// using tld extracted from file name but this tld is not part of the registry
command.inputFile =
Paths.get(tmpDir.resolve(String.format("%s.txt", fileName)).toFile().getPath());
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, command::init);
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
String.format("Could not update premium list %s because it doesn't exist.", fileName));
} }
} }