From 27f431b9cf41df663fb9cc0dccbead1039014079 Mon Sep 17 00:00:00 2001 From: Rachel Guan Date: Fri, 14 May 2021 08:40:03 -0400 Subject: [PATCH] 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 --- .../CreateOrUpdatePremiumListCommand.java | 108 ++--------- .../tools/CreatePremiumListCommand.java | 45 +++-- .../tools/UpdatePremiumListCommand.java | 86 ++++++++- ...ateOrUpdatePremiumListCommandTestCase.java | 77 +++++--- .../tools/CreatePremiumListCommandTest.java | 170 +++++++++-------- .../tools/UpdatePremiumListCommandTest.java | 174 ++++++++++++++---- 6 files changed, 401 insertions(+), 259 deletions(-) diff --git a/core/src/main/java/google/registry/tools/CreateOrUpdatePremiumListCommand.java b/core/src/main/java/google/registry/tools/CreateOrUpdatePremiumListCommand.java index 8a7eb29fc..575aae66d 100644 --- a/core/src/main/java/google/registry/tools/CreateOrUpdatePremiumListCommand.java +++ b/core/src/main/java/google/registry/tools/CreateOrUpdatePremiumListCommand.java @@ -14,40 +14,29 @@ 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.google.common.base.Joiner; -import com.google.common.base.Verify; -import com.google.common.collect.ImmutableMap; -import com.google.common.net.MediaType; -import google.registry.model.registry.label.PremiumList; +import com.google.common.flogger.FluentLogger; +import google.registry.schema.tld.PremiumListSqlDao; import google.registry.tools.params.PathParameter; -import java.net.URLEncoder; -import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import java.util.Map; import javax.annotation.Nullable; -import org.json.simple.JSONValue; /** * Base class for specification of command line parameters common to creating and updating premium * lists. */ -abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand - implements CommandWithConnection, CommandWithRemoteApi { +abstract class CreateOrUpdatePremiumListCommand extends MutatingCommand { + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + protected List inputData; @Nullable @Parameter( names = {"-n", "--name"}, - 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.") + 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.") String name; @Parameter( @@ -57,78 +46,17 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand required = true) Path inputFile; - protected AppEngineConnection connection; - protected int inputLineCount; - - @Override - public void setConnection(AppEngineConnection connection) { - this.connection = connection; - } - - abstract String getCommandPath(); - - ImmutableMap getParameterMap() { - return ImmutableMap.of(); - } - - @Override - protected void init() throws Exception { - name = isNullOrEmpty(name) ? convertFilePathToName(inputFile) : name; - List 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 public String execute() throws Exception { - ImmutableMap.Builder params = new ImmutableMap.Builder<>(); - params.put(NAME_PARAM, name); - String inputFileContents = new String(Files.readAllBytes(inputFile), UTF_8); - String requestBody = - Joiner.on('&').withKeyValueSeparator("=").join( - ImmutableMap.of(INPUT_PARAM, URLEncoder.encode(inputFileContents, UTF_8.toString()))); - - ImmutableMap extraParams = getParameterMap(); - if (extraParams != null) { - params.putAll(extraParams); + String message = String.format("Saved premium list %s with %d entries", name, inputData.size()); + try { + logger.atInfo().log("Saving premium list for TLD %s", name); + PremiumListSqlDao.save(name, inputData); + logger.atInfo().log(message); + } catch (Throwable e) { + message = "Unexpected error saving premium list from nomulus tool command"; + logger.atSevere().withCause(e).log(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 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 toMap(Object obj) { - Verify.verify(obj instanceof Map, "JSON object is not a Map: %s", obj); - return (Map) 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()); + return message; } } diff --git a/core/src/main/java/google/registry/tools/CreatePremiumListCommand.java b/core/src/main/java/google/registry/tools/CreatePremiumListCommand.java index 3eb0a85e6..0096bd896 100644 --- a/core/src/main/java/google/registry/tools/CreatePremiumListCommand.java +++ b/core/src/main/java/google/registry/tools/CreatePremiumListCommand.java @@ -14,14 +14,23 @@ 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.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.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. */ -@Parameters(separators = " =", commandDescription = "Create a PremiumList in Datastore.") +/** Command to create a {@link PremiumList} on Database. */ +@Parameters(separators = " =", commandDescription = "Create a PremiumList in Database.") public class CreatePremiumListCommand extends CreateOrUpdatePremiumListCommand { @Parameter( @@ -29,18 +38,24 @@ public class CreatePremiumListCommand extends CreateOrUpdatePremiumListCommand { description = "Override restrictions on premium list naming") boolean override; - /** Returns the path to the servlet task. */ @Override - public String getCommandPath() { - return CreatePremiumListAction.PATH; - } - - @Override - ImmutableMap getParameterMap() { - if (override) { - return ImmutableMap.of("override", "true"); - } else { - return ImmutableMap.of(); + // Using CreatePremiumListAction.java as reference; + protected void init() throws Exception { + name = Strings.isNullOrEmpty(name) ? convertFilePathToName(inputFile) : name; + checkArgument( + !PremiumListSqlDao.getLatestRevision(name).isPresent(), + "A premium list already exists by this name"); + if (!override) { + // refer to CreatePremiumListAction.java + assertTldExists( + 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))); } } diff --git a/core/src/main/java/google/registry/tools/UpdatePremiumListCommand.java b/core/src/main/java/google/registry/tools/UpdatePremiumListCommand.java index 9bb95f2c8..aa1a33290 100644 --- a/core/src/main/java/google/registry/tools/UpdatePremiumListCommand.java +++ b/core/src/main/java/google/registry/tools/UpdatePremiumListCommand.java @@ -14,18 +14,86 @@ package google.registry.tools; -import com.beust.jcommander.Parameters; -import google.registry.model.registry.label.PremiumList; -import google.registry.tools.server.UpdatePremiumListAction; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableSet.toImmutableSet; +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. */ -@Parameters(separators = " =", commandDescription = "Update a PremiumList in Datastore.") +import com.beust.jcommander.Parameters; +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 { - /** Returns the path to the servlet task. */ @Override - public String getCommandPath() { - return UpdatePremiumListAction.PATH; + // Using UpdatePremiumListAction.java as reference; + protected void init() throws Exception { + name = Strings.isNullOrEmpty(name) ? convertFilePathToName(inputFile) : name; + List 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 getExistingPremiumListEntry(String name) { + Optional list = PremiumListSqlDao.getLatestRevision(name); + checkArgument( + list.isPresent(), + String.format("Could not update premium list %s because it doesn't exist.", name)); + Iterable 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()); } } - diff --git a/core/src/test/java/google/registry/tools/CreateOrUpdatePremiumListCommandTestCase.java b/core/src/test/java/google/registry/tools/CreateOrUpdatePremiumListCommandTestCase.java index af18bdec6..6467a974d 100644 --- a/core/src/test/java/google/registry/tools/CreateOrUpdatePremiumListCommandTestCase.java +++ b/core/src/test/java/google/registry/tools/CreateOrUpdatePremiumListCommandTestCase.java @@ -14,47 +14,64 @@ 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 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.net.MediaType; -import google.registry.testing.UriParameters; +import google.registry.dns.writer.VoidDnsWriter; +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.nio.charset.StandardCharsets; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; +import java.io.IOException; +import org.junit.jupiter.api.BeforeEach; /** Base class for common testing setup for create and update commands for Premium Lists. */ abstract class CreateOrUpdatePremiumListCommandTestCase extends CommandTestCase { - @Captor - ArgumentCaptor> urlParamCaptor; + protected static final String TLD_TEST = "prime"; + protected String premiumTermsPath; + protected String initialPremiumListData; - @Captor - ArgumentCaptor requestBodyCaptor; - - static String generateInputData(String premiumTermsPath) throws Exception { - return Files.asCharSource(new File(premiumTermsPath), StandardCharsets.UTF_8).read(); + @BeforeEach + void beforeEachCreateOrUpdatePremiumListCommandTestCase() throws IOException { + // initial set up for both CreatePremiumListCommand and UpdatePremiumListCommand test cases; + initialPremiumListData = "doge,USD 9090"; + File premiumTermsFile = tmpDir.resolve(TLD_TEST + ".txt").toFile(); + Files.asCharSink(premiumTermsFile, UTF_8).write(initialPremiumListData); + premiumTermsPath = premiumTermsFile.getPath(); } - void verifySentParams( - AppEngineConnection connection, String path, ImmutableMap parameterMap) - throws Exception { - verify(connection) - .sendPostRequest( - eq(path), - urlParamCaptor.capture(), - eq(MediaType.FORM_DATA), - requestBodyCaptor.capture()); - assertThat(new ImmutableMap.Builder() - .putAll(urlParamCaptor.getValue()) - .putAll(UriParameters.parse(new String(requestBodyCaptor.getValue(), UTF_8)).entries()) - .build()) - .containsExactlyEntriesIn(parameterMap); + Registry createRegistry(String tldStr, String premiumListInput) { + Registry registry; + if (premiumListInput != null) { + registry = + newRegistry( + tldStr, + Ascii.toUpperCase(tldStr), + ImmutableSortedMap.of(START_OF_TIME, GENERAL_AVAILABILITY), + TldType.TEST); + persistPremiumList(tldStr, premiumListInput); + persistResource(registry); + } else { + registry = + new Registry.Builder() + .setTldStr(tldStr) + .setPremiumPricingEngine(StaticPremiumListPricingEngine.NAME) + .setDnsWriters(ImmutableSet.of(VoidDnsWriter.NAME)) + .setPremiumList(null) + .build(); + tm().transact(() -> tm().put(registry)); + } + return registry; } } diff --git a/core/src/test/java/google/registry/tools/CreatePremiumListCommandTest.java b/core/src/test/java/google/registry/tools/CreatePremiumListCommandTest.java index bb5471307..08ba27f3c 100644 --- a/core/src/test/java/google/registry/tools/CreatePremiumListCommandTest.java +++ b/core/src/test/java/google/registry/tools/CreatePremiumListCommandTest.java @@ -15,105 +15,125 @@ package google.registry.tools; import static com.google.common.truth.Truth.assertThat; -import static google.registry.request.JsonResponse.JSON_SAFETY_PREFIX; -import static google.registry.testing.TestDataHelper.loadFile; +import static google.registry.testing.DatabaseHelper.createTld; 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.base.VerifyException; -import com.google.common.collect.ImmutableMap; -import com.google.common.net.MediaType; -import google.registry.tools.server.CreatePremiumListAction; +import com.google.common.io.Files; +import google.registry.model.registry.Registry; +import google.registry.schema.tld.PremiumListSqlDao; +import java.nio.file.Path; +import java.nio.file.Paths; import org.junit.jupiter.api.BeforeEach; 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}. */ class CreatePremiumListCommandTest extends CreateOrUpdatePremiumListCommandTestCase { - - @Mock AppEngineConnection connection; - - private String premiumTermsPath; - String premiumTermsCsv; - private String servletPath; + Registry registry; @BeforeEach - void beforeEach() throws Exception { - command.setConnection(connection); - premiumTermsPath = - writeToNamedTmpFile( - "example_premium_terms.csv", - loadFile(CreatePremiumListCommandTest.class, "example_premium_terms.csv")); - servletPath = "/_dr/admin/createPremiumList"; - when(connection.sendPostRequest( - eq(CreatePremiumListAction.PATH), - ArgumentMatchers.anyMap(), - any(MediaType.class), - any(byte[].class))) - .thenReturn(JSON_SAFETY_PREFIX + "{\"status\":\"success\",\"lines\":[]}"); + void beforeEach() { + registry = createRegistry(TLD_TEST, null); } @Test - void testRun() throws Exception { - runCommandForced("-i=" + premiumTermsPath, "-n=foo"); - assertInStdout("Successfully"); - verifySentParams( - connection, - servletPath, - ImmutableMap.of("name", "foo", "inputData", generateInputData(premiumTermsPath))); + void verify_registryIsSetUpCorrectly() { + // ensure that no premium list is created before running the command + // this check also implicitly verifies the TLD is successfully created; + assertThat(PremiumListSqlDao.getLatestRevision(TLD_TEST).isPresent()).isFalse(); } @Test - void testRun_noProvidedName_usesBasenameOfInputFile() throws Exception { - runCommandForced("-i=" + premiumTermsPath); - assertInStdout("Successfully"); - verifySentParams( - connection, - servletPath, - ImmutableMap.of( - "name", "example_premium_terms", "inputData", generateInputData(premiumTermsPath))); + void commandRun_successCreateList() throws Exception { + runCommandForced("--name=" + TLD_TEST, "--input=" + premiumTermsPath); + assertThat(registry.getTld().toString()).isEqualTo(TLD_TEST); + assertThat(PremiumListSqlDao.getLatestRevision(TLD_TEST).isPresent()).isTrue(); } @Test - void testRun_errorResponse() throws Exception { - reset(connection); - command.setConnection(connection); - when(connection.sendPostRequest( - eq(CreatePremiumListAction.PATH), anyMap(), any(MediaType.class), any(byte[].class))) - .thenReturn(JSON_SAFETY_PREFIX + "{\"status\":\"error\",\"error\":\"foo already exists\"}"); - VerifyException thrown = - assertThrows( - VerifyException.class, () -> runCommandForced("-i=" + premiumTermsPath, "-n=foo")); - assertThat(thrown).hasMessageThat().contains("Server error:"); + // since the old entity is always null and file cannot be empty, the prompt will NOT be "No entity + // changes to apply." + void commandInit_successStageNewEntity() throws Exception { + CreatePremiumListCommand command = new CreatePremiumListCommand(); + command.inputFile = Paths.get(premiumTermsPath); + command.init(); + assertThat(command.prompt()).contains("Create PremiumList@"); + assertThat(command.prompt()).contains(String.format("name=%s", TLD_TEST)); } @Test - @MockitoSettings(strictness = Strictness.LENIENT) - void testRun_noInputFileSpecified_throwsException() { - ParameterException thrown = assertThrows(ParameterException.class, this::runCommand); - assertThat(thrown).hasMessageThat().contains("The following option is required"); + void commandInit_successStageNewEntityWithOverride() throws Exception { + CreatePremiumListCommand command = new CreatePremiumListCommand(); + String alterTld = "override"; + 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 - @MockitoSettings(strictness = Strictness.LENIENT) - void testRun_invalidInputData() throws Exception { - premiumTermsPath = - writeToNamedTmpFile( - "tmp_file2", - loadFile(CreatePremiumListCommandTest.class, "example_invalid_premium_terms.csv")); - IllegalArgumentException thrown = - assertThrows( - IllegalArgumentException.class, - () -> runCommandForced("-i=" + premiumTermsPath, "-n=foo")); - assertThat(thrown).hasMessageThat().contains("Could not parse line in premium list"); + void commandInit_failureNoInputFile() { + CreatePremiumListCommand command = new CreatePremiumListCommand(); + assertThrows(NullPointerException.class, command::init); + } + + @Test + void commandInit_failurePremiumListAlreadyExists() { + String randomStr = "random"; + createTld(randomStr); + CreatePremiumListCommand command = new CreatePremiumListCommand(); + 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"); } } diff --git a/core/src/test/java/google/registry/tools/UpdatePremiumListCommandTest.java b/core/src/test/java/google/registry/tools/UpdatePremiumListCommandTest.java index 664f8e55d..5ec85c9cc 100644 --- a/core/src/test/java/google/registry/tools/UpdatePremiumListCommandTest.java +++ b/core/src/test/java/google/registry/tools/UpdatePremiumListCommandTest.java @@ -14,60 +14,154 @@ package google.registry.tools; -import static google.registry.request.JsonResponse.JSON_SAFETY_PREFIX; -import static google.registry.testing.TestDataHelper.loadFile; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertThrows; -import com.google.common.collect.ImmutableMap; -import com.google.common.net.MediaType; -import google.registry.tools.server.UpdatePremiumListAction; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Files; +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.Test; -import org.mockito.Mock; /** Unit tests for {@link UpdatePremiumListCommand}. */ class UpdatePremiumListCommandTest extends CreateOrUpdatePremiumListCommandTestCase { - - @Mock AppEngineConnection connection; - - private String premiumTermsPath; - String premiumTermsCsv; - private String servletPath; + Registry registry; @BeforeEach - void beforeEach() throws Exception { - command.setConnection(connection); - 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\":[]}"); + void beforeEach() { + registry = createRegistry(TLD_TEST, initialPremiumListData); } @Test - void testRun() throws Exception { - runCommandForced("-i=" + premiumTermsPath, "-n=foo"); - verifySentParams( - connection, - servletPath, - ImmutableMap.of("name", "foo", "inputData", generateInputData(premiumTermsPath))); + void verify_registryIsSetUpCorrectly() { + // ensure that no premium list is created before running the command + assertThat(PremiumListSqlDao.getLatestRevision(TLD_TEST).isPresent()).isTrue(); + // ensure that there's value in existing premium list; + UpdatePremiumListCommand command = new UpdatePremiumListCommand(); + ImmutableSet 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 - void testRun_noProvidedName_usesBasenameOfInputFile() throws Exception { - runCommandForced("-i=" + premiumTermsPath); - assertInStdout("Successfully"); - verifySentParams( - connection, - servletPath, - ImmutableMap.of( - "name", "example_premium_terms", "inputData", generateInputData(premiumTermsPath))); + void commandInit_successStageNoEntityChange() throws Exception { + UpdatePremiumListCommand command = new UpdatePremiumListCommand(); + command.inputFile = Paths.get(premiumTermsPath); + command.name = TLD_TEST; + command.init(); + assertThat(command.prompt()).contains("No entity changes to apply."); + } + + @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 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 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)); } }