From 14c72b524f0e87fa0258eec7c6f6ec159f66288a Mon Sep 17 00:00:00 2001 From: Jun Nemoto <35618893+jnmt@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:02:56 +0900 Subject: [PATCH] Refactor client CLIs (#324) --- .../scalar/dl/client/error/ClientError.java | 2 + .../dl/client/tool/AbstractClientCommand.java | 44 +++++++ .../com/scalar/dl/client/tool/Bootstrap.java | 38 +----- .../client/tool/CertificateRegistration.java | 38 +----- .../dl/client/tool/ContractExecution.java | 92 +++++-------- .../dl/client/tool/ContractRegistration.java | 53 ++------ .../dl/client/tool/ContractsListing.java | 38 +----- .../dl/client/tool/ContractsRegistration.java | 121 ++++++++---------- .../dl/client/tool/FunctionRegistration.java | 37 +----- .../dl/client/tool/FunctionsRegistration.java | 99 ++++++-------- .../dl/client/tool/LedgerValidation.java | 38 +----- .../dl/client/tool/SecretRegistration.java | 38 +----- .../scalar/dl/client/tool/BootstrapTest.java | 21 ++- .../tool/CertificateRegistrationTest.java | 14 +- .../dl/client/tool/ContractExecutionTest.java | 24 ++-- .../client/tool/ContractRegistrationTest.java | 28 ++-- .../dl/client/tool/ContractsListingTest.java | 12 +- .../tool/ContractsRegistrationTest.java | 32 ++--- .../client/tool/FunctionRegistrationTest.java | 14 +- .../tool/FunctionsRegistrationTest.java | 26 ++-- .../dl/client/tool/LedgerValidationTest.java | 57 ++++++--- .../client/tool/SecretRegistrationTest.java | 15 ++- .../client/tool/AbstractHashStoreCommand.java | 2 +- .../tool/AbstractTableStoreCommand.java | 46 +++++++ .../dl/tablestore/client/tool/Bootstrap.java | 39 +----- .../client/tool/LedgerValidation.java | 53 ++------ .../client/tool/StatementExecution.java | 51 ++------ .../tablestore/client/tool/BootstrapTest.java | 16 +-- .../client/tool/LedgerValidationTest.java | 27 ++-- .../client/tool/StatementExecutionTest.java | 19 ++- 30 files changed, 454 insertions(+), 680 deletions(-) create mode 100644 client/src/main/java/com/scalar/dl/client/tool/AbstractClientCommand.java create mode 100644 table-store/src/main/java/com/scalar/dl/tablestore/client/tool/AbstractTableStoreCommand.java diff --git a/client/src/main/java/com/scalar/dl/client/error/ClientError.java b/client/src/main/java/com/scalar/dl/client/error/ClientError.java index 55588e6a..d89331c4 100644 --- a/client/src/main/java/com/scalar/dl/client/error/ClientError.java +++ b/client/src/main/java/com/scalar/dl/client/error/ClientError.java @@ -116,6 +116,8 @@ public enum ClientError implements ScalarDlError { StatusCode.RUNTIME_ERROR, "004", "Processing JSON failed. Details: %s", "", ""), CLASS_FILE_LOAD_FAILED( StatusCode.RUNTIME_ERROR, "005", "Failed to load the class file. File: %s", "", ""), + WRITING_RESULT_TO_FILE_FAILED( + StatusCode.RUNTIME_ERROR, "006", "Failed to write the result to a file. Details: %s", "", ""), ; private static final String COMPONENT_NAME = "DL-CLIENT"; diff --git a/client/src/main/java/com/scalar/dl/client/tool/AbstractClientCommand.java b/client/src/main/java/com/scalar/dl/client/tool/AbstractClientCommand.java new file mode 100644 index 00000000..eb10de68 --- /dev/null +++ b/client/src/main/java/com/scalar/dl/client/tool/AbstractClientCommand.java @@ -0,0 +1,44 @@ +package com.scalar.dl.client.tool; + +import com.google.common.annotations.VisibleForTesting; +import com.scalar.dl.client.config.ClientConfig; +import com.scalar.dl.client.config.GatewayClientConfig; +import com.scalar.dl.client.exception.ClientException; +import com.scalar.dl.client.service.ClientService; +import com.scalar.dl.client.service.ClientServiceFactory; +import java.io.File; +import java.util.concurrent.Callable; + +public abstract class AbstractClientCommand extends CommonOptions implements Callable { + + @Override + public final Integer call() throws Exception { + return call(new ClientServiceFactory()); + } + + @VisibleForTesting + public final Integer call(ClientServiceFactory factory) throws Exception { + try { + ClientService service = + useGateway + ? factory.create(new GatewayClientConfig(new File(properties))) + : factory.create(new ClientConfig(new File(properties))); + return execute(service); + } catch (ClientException e) { + Common.printError(e); + printStackTrace(e); + return 1; + } finally { + factory.close(); + } + } + + /** + * Executes the specific command logic. + * + * @param service the client service to use for execution. + * @return the exit code. + * @throws ClientException if the execution fails. + */ + protected abstract Integer execute(ClientService service) throws ClientException; +} diff --git a/client/src/main/java/com/scalar/dl/client/tool/Bootstrap.java b/client/src/main/java/com/scalar/dl/client/tool/Bootstrap.java index fb8f1c6e..ee7641cc 100644 --- a/client/src/main/java/com/scalar/dl/client/tool/Bootstrap.java +++ b/client/src/main/java/com/scalar/dl/client/tool/Bootstrap.java @@ -1,46 +1,18 @@ package com.scalar.dl.client.tool; -import com.google.common.annotations.VisibleForTesting; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.service.ClientService; -import com.scalar.dl.client.service.ClientServiceFactory; -import java.io.File; -import java.util.concurrent.Callable; import picocli.CommandLine.Command; @Command( name = "bootstrap", description = "Bootstrap the ledger by registering identity and system contracts.") -public class Bootstrap extends CommonOptions implements Callable { +public class Bootstrap extends AbstractClientCommand { @Override - public Integer call() throws Exception { - return call(new ClientServiceFactory()); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory) throws Exception { - ClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties))) - : factory.create(new ClientConfig(new File(properties))); - return call(factory, service); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory, ClientService service) { - try { - service.bootstrap(); - Common.printOutput(null); - return 0; - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - return 1; - } finally { - factory.close(); - } + protected Integer execute(ClientService service) throws ClientException { + service.bootstrap(); + Common.printOutput(null); + return 0; } } diff --git a/client/src/main/java/com/scalar/dl/client/tool/CertificateRegistration.java b/client/src/main/java/com/scalar/dl/client/tool/CertificateRegistration.java index 317a9e9c..71bc9350 100644 --- a/client/src/main/java/com/scalar/dl/client/tool/CertificateRegistration.java +++ b/client/src/main/java/com/scalar/dl/client/tool/CertificateRegistration.java @@ -1,18 +1,12 @@ package com.scalar.dl.client.tool; -import com.google.common.annotations.VisibleForTesting; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.service.ClientService; -import com.scalar.dl.client.service.ClientServiceFactory; -import java.io.File; -import java.util.concurrent.Callable; import picocli.CommandLine; import picocli.CommandLine.Command; @Command(name = "register-cert", description = "Register a specified certificate.") -public class CertificateRegistration extends CommonOptions implements Callable { +public class CertificateRegistration extends AbstractClientCommand { public static void main(String[] args) { int exitCode = new CommandLine(new CertificateRegistration()).execute(args); @@ -20,31 +14,9 @@ public static void main(String[] args) { } @Override - public Integer call() throws Exception { - return call(new ClientServiceFactory()); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory) throws Exception { - ClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties))) - : factory.create(new ClientConfig(new File(properties))); - return call(factory, service); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory, ClientService service) { - try { - service.registerCertificate(); - Common.printOutput(null); - return 0; - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - return 1; - } finally { - factory.close(); - } + protected Integer execute(ClientService service) throws ClientException { + service.registerCertificate(); + Common.printOutput(null); + return 0; } } diff --git a/client/src/main/java/com/scalar/dl/client/tool/ContractExecution.java b/client/src/main/java/com/scalar/dl/client/tool/ContractExecution.java index 29fe0531..373daf03 100644 --- a/client/src/main/java/com/scalar/dl/client/tool/ContractExecution.java +++ b/client/src/main/java/com/scalar/dl/client/tool/ContractExecution.java @@ -2,21 +2,15 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.annotations.VisibleForTesting; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.service.ClientService; -import com.scalar.dl.client.service.ClientServiceFactory; import com.scalar.dl.ledger.model.ContractExecutionResult; import com.scalar.dl.ledger.util.JacksonSerDe; -import java.io.File; -import java.util.concurrent.Callable; import picocli.CommandLine; import picocli.CommandLine.Command; @Command(name = "execute-contract", description = "Execute a specified contract.") -public class ContractExecution extends CommonOptions implements Callable { +public class ContractExecution extends AbstractClientCommand { @CommandLine.Option( names = {"--contract-id"}, @@ -61,63 +55,41 @@ public static void main(String[] args) { } @Override - public Integer call() throws Exception { - return call(new ClientServiceFactory()); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory) throws Exception { - ClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties))) - : factory.create(new ClientConfig(new File(properties))); - return call(factory, service); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory, ClientService service) throws Exception { + protected Integer execute(ClientService service) throws ClientException { JacksonSerDe serde = new JacksonSerDe(new ObjectMapper()); - try { - if (deserializationFormat == DeserializationFormat.JSON) { - JsonNode jsonContractArgument = serde.deserialize(contractArgument); - JsonNode jsonFunctionArgument = null; - if (functionArgument != null) { - jsonFunctionArgument = serde.deserialize(functionArgument); - } - ContractExecutionResult result = - service.executeContract( - contractId, jsonContractArgument, functionId, jsonFunctionArgument); - - result - .getContractResult() - .ifPresent( - r -> { - System.out.println("Contract result:"); - Common.printJson(serde.deserialize(r)); - }); - result - .getFunctionResult() - .ifPresent( - r -> { - System.out.println("Function result:"); - Common.printJson(serde.deserialize(r)); - }); - } else if (deserializationFormat == DeserializationFormat.STRING) { - ContractExecutionResult result = - service.executeContract(contractId, contractArgument, functionId, functionArgument); - - result.getContractResult().ifPresent(r -> System.out.println("Contract result: " + r)); - result.getFunctionResult().ifPresent(r -> System.out.println("Function result: " + r)); + if (deserializationFormat == DeserializationFormat.JSON) { + JsonNode jsonContractArgument = serde.deserialize(contractArgument); + JsonNode jsonFunctionArgument = null; + if (functionArgument != null) { + jsonFunctionArgument = serde.deserialize(functionArgument); } + ContractExecutionResult result = + service.executeContract( + contractId, jsonContractArgument, functionId, jsonFunctionArgument); - return 0; - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - return 1; - } finally { - factory.close(); + result + .getContractResult() + .ifPresent( + r -> { + System.out.println("Contract result:"); + Common.printJson(serde.deserialize(r)); + }); + result + .getFunctionResult() + .ifPresent( + r -> { + System.out.println("Function result:"); + Common.printJson(serde.deserialize(r)); + }); + } else if (deserializationFormat == DeserializationFormat.STRING) { + ContractExecutionResult result = + service.executeContract(contractId, contractArgument, functionId, functionArgument); + + result.getContractResult().ifPresent(r -> System.out.println("Contract result: " + r)); + result.getFunctionResult().ifPresent(r -> System.out.println("Function result: " + r)); } + + return 0; } } diff --git a/client/src/main/java/com/scalar/dl/client/tool/ContractRegistration.java b/client/src/main/java/com/scalar/dl/client/tool/ContractRegistration.java index 5069343a..dbd2b326 100644 --- a/client/src/main/java/com/scalar/dl/client/tool/ContractRegistration.java +++ b/client/src/main/java/com/scalar/dl/client/tool/ContractRegistration.java @@ -2,20 +2,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.annotations.VisibleForTesting; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.service.ClientService; -import com.scalar.dl.client.service.ClientServiceFactory; import com.scalar.dl.ledger.util.JacksonSerDe; -import java.io.File; -import java.util.concurrent.Callable; import picocli.CommandLine; import picocli.CommandLine.Command; @Command(name = "register-contract", description = "Register a specified contract.") -public class ContractRegistration extends CommonOptions implements Callable { +public class ContractRegistration extends AbstractClientCommand { @CommandLine.Option( names = {"--contract-id"}, @@ -60,42 +54,21 @@ public static void main(String[] args) { } @Override - public Integer call() throws Exception { - return call(new ClientServiceFactory()); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory) throws Exception { - ClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties))) - : factory.create(new ClientConfig(new File(properties))); - return call(factory, service); - } - - Integer call(ClientServiceFactory factory, ClientService service) { + protected Integer execute(ClientService service) throws ClientException { JacksonSerDe serde = new JacksonSerDe(new ObjectMapper()); - try { - if (deserializationFormat == DeserializationFormat.JSON) { - JsonNode jsonContractProperties = null; - if (contractProperties != null) { - jsonContractProperties = serde.deserialize(contractProperties); - } - service.registerContract( - contractId, contractBinaryName, contractClassFile, jsonContractProperties); - } else if (deserializationFormat == DeserializationFormat.STRING) { - service.registerContract( - contractId, contractBinaryName, contractClassFile, contractProperties); + if (deserializationFormat == DeserializationFormat.JSON) { + JsonNode jsonContractProperties = null; + if (contractProperties != null) { + jsonContractProperties = serde.deserialize(contractProperties); } - Common.printOutput(null); - return 0; - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - return 1; - } finally { - factory.close(); + service.registerContract( + contractId, contractBinaryName, contractClassFile, jsonContractProperties); + } else if (deserializationFormat == DeserializationFormat.STRING) { + service.registerContract( + contractId, contractBinaryName, contractClassFile, contractProperties); } + Common.printOutput(null); + return 0; } } diff --git a/client/src/main/java/com/scalar/dl/client/tool/ContractsListing.java b/client/src/main/java/com/scalar/dl/client/tool/ContractsListing.java index b5bbb46b..e28ece85 100644 --- a/client/src/main/java/com/scalar/dl/client/tool/ContractsListing.java +++ b/client/src/main/java/com/scalar/dl/client/tool/ContractsListing.java @@ -1,21 +1,15 @@ package com.scalar.dl.client.tool; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.annotations.VisibleForTesting; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.service.ClientService; -import com.scalar.dl.client.service.ClientServiceFactory; import com.scalar.dl.ledger.util.JacksonSerDe; -import java.io.File; -import java.util.concurrent.Callable; import javax.json.JsonObject; import picocli.CommandLine; import picocli.CommandLine.Command; @Command(name = "list-contracts", description = "List registered contracts.") -public class ContractsListing extends CommonOptions implements Callable { +public class ContractsListing extends AbstractClientCommand { @CommandLine.Option( names = {"--contract-id"}, @@ -30,32 +24,10 @@ public static void main(String[] args) { } @Override - public Integer call() throws Exception { - return call(new ClientServiceFactory()); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory) throws Exception { - ClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties))) - : factory.create(new ClientConfig(new File(properties))); - return call(factory, service); - } - - Integer call(ClientServiceFactory factory, ClientService service) { + protected Integer execute(ClientService service) throws ClientException { JacksonSerDe serde = new JacksonSerDe(new ObjectMapper()); - - try { - JsonObject result = service.listContracts(contractId); - Common.printOutput(serde.deserialize(result.toString())); - return 0; - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - return 1; - } finally { - factory.close(); - } + JsonObject result = service.listContracts(contractId); + Common.printOutput(serde.deserialize(result.toString())); + return 0; } } diff --git a/client/src/main/java/com/scalar/dl/client/tool/ContractsRegistration.java b/client/src/main/java/com/scalar/dl/client/tool/ContractsRegistration.java index 0a102fbd..151905d1 100644 --- a/client/src/main/java/com/scalar/dl/client/tool/ContractsRegistration.java +++ b/client/src/main/java/com/scalar/dl/client/tool/ContractsRegistration.java @@ -2,28 +2,26 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.annotations.VisibleForTesting; import com.moandjiezana.toml.Toml; import com.moandjiezana.toml.TomlWriter; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; +import com.scalar.dl.client.error.ClientError; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.service.ClientService; -import com.scalar.dl.client.service.ClientServiceFactory; import com.scalar.dl.ledger.service.StatusCode; import com.scalar.dl.ledger.util.JacksonSerDe; import java.io.File; -import java.io.FileOutputStream; +import java.io.IOException; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Callable; import picocli.CommandLine; import picocli.CommandLine.Command; @Command(name = "register-contracts", description = "Register specified contracts.") -public class ContractsRegistration extends CommonOptions implements Callable { +public class ContractsRegistration extends AbstractClientCommand { static final String REGISTRATION_FAILED_CONTRACTS_TOML_FILE = "registration-failed-contracts.toml"; static final String TOML_TABLES_NAME = "contracts"; @@ -41,73 +39,52 @@ public static void main(String[] args) { } @Override - public Integer call() throws Exception { - ClientServiceFactory factory = new ClientServiceFactory(); - File contractsFileObj = new File(contractsFile); - return call(factory, contractsFileObj); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory, File contractsFile) throws Exception { - ClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties))) - : factory.create(new ClientConfig(new File(properties))); - return call(factory, service, contractsFile); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory, ClientService service, File contractsFile) - throws Exception { + protected Integer execute(ClientService service) throws ClientException { JacksonSerDe serde = new JacksonSerDe(new ObjectMapper()); - List succeeded = new ArrayList(); - List alreadyRegistered = new ArrayList(); - List failed = new ArrayList(); - - try { - new Toml() - .read(contractsFile) - .getTables(TOML_TABLES_NAME) - .forEach( - each -> { - String id = each.getString("contract-id"); - String binaryName = each.getString("contract-binary-name"); - String classFile = each.getString("contract-class-file"); - - // All of them are required values to register a function. - // Thus, the malformed table is skipped if one of them doesn't exist. - if (id == null || binaryName == null || classFile == null) { + List succeeded = new ArrayList<>(); + List alreadyRegistered = new ArrayList<>(); + List failed = new ArrayList<>(); + + new Toml() + .read(new File(contractsFile)) + .getTables(TOML_TABLES_NAME) + .forEach( + each -> { + String id = each.getString("contract-id"); + String binaryName = each.getString("contract-binary-name"); + String classFile = each.getString("contract-class-file"); + + // All of them are required values to register a function. + // Thus, the malformed table is skipped if one of them doesn't exist. + if (id == null || binaryName == null || classFile == null) { + failed.add(each); + return; + } + + JsonNode properties = null; + + if (each.contains("contract-properties")) { + properties = serde.deserialize(each.getString("contract-properties")); + } else if (each.contains("properties")) { + properties = serde.deserialize(each.getString("properties")); + } + + try { + System.out.printf("Register contract %s as %s%n", binaryName, id); + service.registerContract(id, binaryName, classFile, properties); + Common.printOutput(null); + succeeded.add(each); + } catch (ClientException e) { + Common.printError(e); + printStackTrace(e); + if (e.getStatusCode() == StatusCode.CONTRACT_ALREADY_REGISTERED) { + alreadyRegistered.add(each); + } else { failed.add(each); - return; - } - - JsonNode properties = null; - - if (each.contains("contract-properties")) { - properties = serde.deserialize(each.getString("contract-properties")); - } else if (each.contains("properties")) { - properties = serde.deserialize(each.getString("properties")); } - - try { - System.out.printf("Register contract %s as %s%n", binaryName, id); - service.registerContract(id, binaryName, classFile, properties); - Common.printOutput(null); - succeeded.add(each); - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - if (e.getStatusCode() == StatusCode.CONTRACT_ALREADY_REGISTERED) { - alreadyRegistered.add(each); - } else { - failed.add(each); - } - } - }); - } finally { - factory.close(); - } + } + }); System.out.printf("Registration succeeded: %d%n", succeeded.size()); System.out.printf("Already registered: %d%n", alreadyRegistered.size()); @@ -116,7 +93,7 @@ Integer call(ClientServiceFactory factory, ClientService service, File contracts if (!failed.isEmpty()) { try (OutputStreamWriter fileWriter = new OutputStreamWriter( - new FileOutputStream(REGISTRATION_FAILED_CONTRACTS_TOML_FILE), + Files.newOutputStream(Paths.get(REGISTRATION_FAILED_CONTRACTS_TOML_FILE)), StandardCharsets.UTF_8)) { TomlWriter tomlWriter = new TomlWriter(); @@ -124,6 +101,8 @@ Integer call(ClientServiceFactory factory, ClientService service, File contracts fileWriter.write(String.format("[[%s]]%n", TOML_TABLES_NAME)); tomlWriter.write(toml.toMap(), fileWriter); } + } catch (IOException e) { + throw new ClientException(ClientError.WRITING_RESULT_TO_FILE_FAILED, e, e.getMessage()); } System.out.printf( diff --git a/client/src/main/java/com/scalar/dl/client/tool/FunctionRegistration.java b/client/src/main/java/com/scalar/dl/client/tool/FunctionRegistration.java index 77ff9377..e89f244a 100644 --- a/client/src/main/java/com/scalar/dl/client/tool/FunctionRegistration.java +++ b/client/src/main/java/com/scalar/dl/client/tool/FunctionRegistration.java @@ -1,18 +1,12 @@ package com.scalar.dl.client.tool; -import com.google.common.annotations.VisibleForTesting; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.service.ClientService; -import com.scalar.dl.client.service.ClientServiceFactory; -import java.io.File; -import java.util.concurrent.Callable; import picocli.CommandLine; import picocli.CommandLine.Command; @Command(name = "register-function", description = "Register a specified function.") -public class FunctionRegistration extends CommonOptions implements Callable { +public class FunctionRegistration extends AbstractClientCommand { @CommandLine.Option( names = {"--function-id"}, @@ -41,30 +35,9 @@ public static void main(String[] args) { } @Override - public Integer call() throws Exception { - return call(new ClientServiceFactory()); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory) throws Exception { - ClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties))) - : factory.create(new ClientConfig(new File(properties))); - return call(factory, service); - } - - Integer call(ClientServiceFactory factory, ClientService service) { - try { - service.registerFunction(functionId, functionBinaryName, functionClassFile); - Common.printOutput(null); - return 0; - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - return 1; - } finally { - factory.close(); - } + protected Integer execute(ClientService service) throws ClientException { + service.registerFunction(functionId, functionBinaryName, functionClassFile); + Common.printOutput(null); + return 0; } } diff --git a/client/src/main/java/com/scalar/dl/client/tool/FunctionsRegistration.java b/client/src/main/java/com/scalar/dl/client/tool/FunctionsRegistration.java index 06cce91b..e6f32323 100644 --- a/client/src/main/java/com/scalar/dl/client/tool/FunctionsRegistration.java +++ b/client/src/main/java/com/scalar/dl/client/tool/FunctionsRegistration.java @@ -1,25 +1,23 @@ package com.scalar.dl.client.tool; -import com.google.common.annotations.VisibleForTesting; import com.moandjiezana.toml.Toml; import com.moandjiezana.toml.TomlWriter; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; +import com.scalar.dl.client.error.ClientError; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.service.ClientService; -import com.scalar.dl.client.service.ClientServiceFactory; import java.io.File; -import java.io.FileOutputStream; +import java.io.IOException; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Callable; import picocli.CommandLine; import picocli.CommandLine.Command; @Command(name = "register-functions", description = "Register specified functions.") -public class FunctionsRegistration extends CommonOptions implements Callable { +public class FunctionsRegistration extends AbstractClientCommand { static final String REGISTRATION_FAILED_FUNCTIONS_TOML_FILE = "registration-failed-functions.toml"; static final String TOML_TABLES_NAME = "functions"; @@ -38,58 +36,37 @@ public static void main(String[] args) { } @Override - public Integer call() throws Exception { - ClientServiceFactory factory = new ClientServiceFactory(); - File functionsFileObj = new File(functionsFile); - return call(factory, functionsFileObj); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory, File functionsFile) throws Exception { - ClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties))) - : factory.create(new ClientConfig(new File(properties))); - return call(factory, service, functionsFile); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory, ClientService service, File functionsFile) - throws Exception { - List succeeded = new ArrayList(); - List failed = new ArrayList(); - - try { - new Toml() - .read(functionsFile) - .getTables(TOML_TABLES_NAME) - .forEach( - each -> { - String id = each.getString("function-id"); - String binaryName = each.getString("function-binary-name"); - String classFile = each.getString("function-class-file"); - - // All of them are required values to register a function. - // Thus, the malformed table is skipped if one of them doesn't exist. - if (id == null || binaryName == null || classFile == null) { - failed.add(each); - return; - } - - try { - System.out.printf("Register function %s as %s%n", binaryName, id); - service.registerFunction(id, binaryName, classFile); - Common.printOutput(null); - succeeded.add(each); - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - failed.add(each); - } - }); - } finally { - factory.close(); - } + protected Integer execute(ClientService service) throws ClientException { + List succeeded = new ArrayList<>(); + List failed = new ArrayList<>(); + + new Toml() + .read(new File(functionsFile)) + .getTables(TOML_TABLES_NAME) + .forEach( + each -> { + String id = each.getString("function-id"); + String binaryName = each.getString("function-binary-name"); + String classFile = each.getString("function-class-file"); + + // All of them are required values to register a function. + // Thus, the malformed table is skipped if one of them doesn't exist. + if (id == null || binaryName == null || classFile == null) { + failed.add(each); + return; + } + + try { + System.out.printf("Register function %s as %s%n", binaryName, id); + service.registerFunction(id, binaryName, classFile); + Common.printOutput(null); + succeeded.add(each); + } catch (ClientException e) { + Common.printError(e); + printStackTrace(e); + failed.add(each); + } + }); System.out.printf("Registration succeeded: %d%n", succeeded.size()); System.out.printf("Registration failed: %d%n", failed.size()); @@ -97,7 +74,7 @@ Integer call(ClientServiceFactory factory, ClientService service, File functions if (!failed.isEmpty()) { try (OutputStreamWriter fileWriter = new OutputStreamWriter( - new FileOutputStream(REGISTRATION_FAILED_FUNCTIONS_TOML_FILE), + Files.newOutputStream(Paths.get(REGISTRATION_FAILED_FUNCTIONS_TOML_FILE)), StandardCharsets.UTF_8)) { TomlWriter tomlWriter = new TomlWriter(); @@ -105,6 +82,8 @@ Integer call(ClientServiceFactory factory, ClientService service, File functions fileWriter.write(String.format("[[%s]]%n", TOML_TABLES_NAME)); tomlWriter.write(toml.toMap(), fileWriter); } + } catch (IOException e) { + throw new ClientException(ClientError.WRITING_RESULT_TO_FILE_FAILED, e, e.getMessage()); } System.out.printf( diff --git a/client/src/main/java/com/scalar/dl/client/tool/LedgerValidation.java b/client/src/main/java/com/scalar/dl/client/tool/LedgerValidation.java index e92c52f9..58794a7d 100644 --- a/client/src/main/java/com/scalar/dl/client/tool/LedgerValidation.java +++ b/client/src/main/java/com/scalar/dl/client/tool/LedgerValidation.java @@ -1,22 +1,16 @@ package com.scalar.dl.client.tool; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.error.ClientError; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.service.ClientService; -import com.scalar.dl.client.service.ClientServiceFactory; import com.scalar.dl.ledger.model.LedgerValidationResult; -import java.io.File; import java.util.List; -import java.util.concurrent.Callable; import picocli.CommandLine; import picocli.CommandLine.Command; @Command(name = "validate-ledger", description = "Validate a specified asset in a ledger.") -public class LedgerValidation extends CommonOptions implements Callable { +public class LedgerValidation extends AbstractClientCommand { @CommandLine.Option( names = {"--asset-id"}, @@ -32,21 +26,7 @@ public static void main(String[] args) { } @Override - public Integer call() throws Exception { - return call(new ClientServiceFactory()); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory) throws Exception { - ClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties))) - : factory.create(new ClientConfig(new File(properties))); - return call(factory, service); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory, ClientService service) { + protected Integer execute(ClientService service) throws ClientException { try { assetIds.forEach( assetId -> { @@ -66,20 +46,10 @@ Integer call(ClientServiceFactory factory, ClientService service) { Common.printJson(Common.getValidationResult(result)); }); return 0; - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - return 1; } catch (NumberFormatException e) { - System.out.println(ClientError.OPTION_ASSET_ID_CONTAINS_INVALID_INTEGER); - printStackTrace(e); - return 1; + throw new ClientException(ClientError.OPTION_ASSET_ID_CONTAINS_INVALID_INTEGER, e); } catch (IndexOutOfBoundsException e) { - System.out.println(ClientError.OPTION_ASSET_ID_IS_MALFORMED.buildMessage()); - printStackTrace(e); - return 1; - } finally { - factory.close(); + throw new ClientException(ClientError.OPTION_ASSET_ID_IS_MALFORMED, e); } } } diff --git a/client/src/main/java/com/scalar/dl/client/tool/SecretRegistration.java b/client/src/main/java/com/scalar/dl/client/tool/SecretRegistration.java index 122c1864..1beea8a6 100644 --- a/client/src/main/java/com/scalar/dl/client/tool/SecretRegistration.java +++ b/client/src/main/java/com/scalar/dl/client/tool/SecretRegistration.java @@ -1,18 +1,12 @@ package com.scalar.dl.client.tool; -import com.google.common.annotations.VisibleForTesting; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.service.ClientService; -import com.scalar.dl.client.service.ClientServiceFactory; -import java.io.File; -import java.util.concurrent.Callable; import picocli.CommandLine; import picocli.CommandLine.Command; @Command(name = "register-secret", description = "Register a specified secret.") -public class SecretRegistration extends CommonOptions implements Callable { +public class SecretRegistration extends AbstractClientCommand { public static void main(String[] args) { int exitCode = new CommandLine(new SecretRegistration()).execute(args); @@ -20,31 +14,9 @@ public static void main(String[] args) { } @Override - public Integer call() throws Exception { - return call(new ClientServiceFactory()); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory) throws Exception { - ClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties))) - : factory.create(new ClientConfig(new File(properties))); - return call(factory, service); - } - - @VisibleForTesting - Integer call(ClientServiceFactory factory, ClientService service) { - try { - service.registerSecret(); - Common.printOutput(null); - return 0; - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - return 1; - } finally { - factory.close(); - } + protected Integer execute(ClientService service) throws ClientException { + service.registerSecret(); + Common.printOutput(null); + return 0; } } diff --git a/client/src/test/java/com/scalar/dl/client/tool/BootstrapTest.java b/client/src/test/java/com/scalar/dl/client/tool/BootstrapTest.java index e8bb46d1..284699f3 100644 --- a/client/src/test/java/com/scalar/dl/client/tool/BootstrapTest.java +++ b/client/src/test/java/com/scalar/dl/client/tool/BootstrapTest.java @@ -1,6 +1,7 @@ package com.scalar.dl.client.tool; import static com.scalar.dl.client.tool.CommandLineTestUtils.createDefaultClientPropertiesFile; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.Mockito.*; @@ -10,7 +11,9 @@ import com.scalar.dl.client.service.ClientService; import com.scalar.dl.client.service.ClientServiceFactory; import com.scalar.dl.ledger.service.StatusCode; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.PrintStream; import java.nio.file.Path; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -21,10 +24,12 @@ public class BootstrapTest { private CommandLine commandLine; + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); @BeforeEach - void setup() { + void setup() throws Exception { commandLine = new CommandLine(new Bootstrap()); + System.setOut(new PrintStream(outputStreamCaptor, true, UTF_8.name())); } @Nested @@ -32,7 +37,7 @@ void setup() { class call { @Test @DisplayName("returns 0 as exit code") - void returns0AsExitCode() { + void returns0AsExitCode() throws ClientException { // Arrange String[] args = new String[] { @@ -40,11 +45,10 @@ void returns0AsExitCode() { "--properties=PROPERTIES_FILE", }; Bootstrap command = parseArgs(args); - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -114,24 +118,27 @@ public void createClientServiceWithClientConfig(@TempDir Path tempDir) throws Ex class whereClientExceptionIsThrownByClientService { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), }; Bootstrap command = parseArgs(args); // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); doThrow(new ClientException("", StatusCode.RUNTIME_ERROR)).when(serviceMock).bootstrap(); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); } } } diff --git a/client/src/test/java/com/scalar/dl/client/tool/CertificateRegistrationTest.java b/client/src/test/java/com/scalar/dl/client/tool/CertificateRegistrationTest.java index e3f61d8c..60613a9f 100644 --- a/client/src/test/java/com/scalar/dl/client/tool/CertificateRegistrationTest.java +++ b/client/src/test/java/com/scalar/dl/client/tool/CertificateRegistrationTest.java @@ -32,7 +32,7 @@ void setup() { class call { @Test @DisplayName("returns 0 as exit code") - void returns0AsExitCode() { + void returns0AsExitCode() throws ClientException { // Arrange String[] args = new String[] { @@ -41,11 +41,10 @@ void returns0AsExitCode() { }; CertificateRegistration command = parseArgs(args); // Mock service that returns ContractExecutionResult. - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -114,26 +113,29 @@ public void createClientServiceWithClientConfig(@TempDir Path tempDir) throws Ex class whereClientExceptionIsThrownByClientService { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), }; CertificateRegistration command = parseArgs(args); // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); doThrow(new ClientException("", StatusCode.RUNTIME_ERROR)) .when(serviceMock) .registerCertificate(); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); } } } diff --git a/client/src/test/java/com/scalar/dl/client/tool/ContractExecutionTest.java b/client/src/test/java/com/scalar/dl/client/tool/ContractExecutionTest.java index 59cc2a38..bf9fc7ff 100644 --- a/client/src/test/java/com/scalar/dl/client/tool/ContractExecutionTest.java +++ b/client/src/test/java/com/scalar/dl/client/tool/ContractExecutionTest.java @@ -64,7 +64,6 @@ void returns0AsExitCode() throws Exception { }; ContractExecution command = parseArgs(args); // Mock service that returns ContractExecutionResult. - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); ContractExecutionResult result = new ContractExecutionResult( @@ -74,7 +73,7 @@ void returns0AsExitCode() throws Exception { .thenReturn(result); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -106,7 +105,6 @@ void returns0AsExitCode() throws Exception { }; ContractExecution command = parseArgs(args); // Mock service that returns ContractExecutionResult. - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); ContractExecutionResult result = new ContractExecutionResult( @@ -119,7 +117,7 @@ void returns0AsExitCode() throws Exception { .thenReturn(result); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -220,12 +218,13 @@ class whereClientExceptionIsThrownByClientService { class withJsonDeserializationFormat { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() throws Exception { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), "--contract-id=CONTRACT_ID", "--contract-argument={}", // By default, JSON is the deserialization format. @@ -234,15 +233,17 @@ void returns1AsExitCode() throws Exception { // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); when(serviceMock.executeContract( eq("CONTRACT_ID"), any(JsonNode.class), isNull(), isNull())) .thenThrow(new ClientException("", StatusCode.RUNTIME_ERROR)); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); } } @@ -251,12 +252,13 @@ void returns1AsExitCode() throws Exception { class withStringDeserializationFormat { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() throws Exception { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), "--contract-id=CONTRACT_ID", "--contract-argument=CONTRACT_ARGUMENT", // Use String as deserialization format. @@ -266,6 +268,7 @@ void returns1AsExitCode() throws Exception { // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); when(serviceMock.executeContract( eq("CONTRACT_ID"), eq("CONTRACT_ARGUMENT"), @@ -274,10 +277,11 @@ void returns1AsExitCode() throws Exception { .thenThrow(new ClientException("", StatusCode.RUNTIME_ERROR)); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); } } } diff --git a/client/src/test/java/com/scalar/dl/client/tool/ContractRegistrationTest.java b/client/src/test/java/com/scalar/dl/client/tool/ContractRegistrationTest.java index 8a59990c..b672ddac 100644 --- a/client/src/test/java/com/scalar/dl/client/tool/ContractRegistrationTest.java +++ b/client/src/test/java/com/scalar/dl/client/tool/ContractRegistrationTest.java @@ -38,7 +38,7 @@ class call { class withJsonDeserializationFormat { @Test @DisplayName("returns 0 as exit code") - void returns0AsExitCode() { + void returns0AsExitCode() throws ClientException { // Arrange String[] args = new String[] { @@ -51,11 +51,10 @@ void returns0AsExitCode() { "--contract-properties=[\"CONTRACT_PROPERTIES\"]", }; ContractRegistration command = parseArgs(args); - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -74,7 +73,7 @@ void returns0AsExitCode() { class withStringDeserializationFormat { @Test @DisplayName("returns 0 as exit code") - void returns0AsExitCode() { + void returns0AsExitCode() throws ClientException { // Arrange String[] args = new String[] { @@ -89,11 +88,10 @@ void returns0AsExitCode() { "--deserialization-format=STRING", }; ContractRegistration command = parseArgs(args); - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -180,12 +178,13 @@ class whereClientExceptionIsThrownByClientService { class withJsonDeserializationFormat { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), "--contract-id=CONTRACT_ID", "--contract-binary-name=CONTRACT_BINARY_NAME", "--contract-class-file=CONTRACT_CLASS_FILE", @@ -196,6 +195,7 @@ void returns1AsExitCode() { // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); doThrow(new ClientException("", StatusCode.RUNTIME_ERROR)) .when(serviceMock) .registerContract( @@ -205,10 +205,11 @@ void returns1AsExitCode() { any(ArrayNode.class)); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); } } @@ -217,12 +218,13 @@ void returns1AsExitCode() { class withStringDeserializationFormat { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), "--contract-id=CONTRACT_ID", "--contract-binary-name=CONTRACT_BINARY_NAME", "--contract-class-file=CONTRACT_CLASS_FILE", @@ -235,6 +237,7 @@ void returns1AsExitCode() { // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); doThrow(new ClientException("", StatusCode.RUNTIME_ERROR)) .when(serviceMock) .registerContract( @@ -244,10 +247,11 @@ void returns1AsExitCode() { eq("CONTRACT_PROPERTIES")); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); } } } diff --git a/client/src/test/java/com/scalar/dl/client/tool/ContractsListingTest.java b/client/src/test/java/com/scalar/dl/client/tool/ContractsListingTest.java index 0264b1a9..aaf3192d 100644 --- a/client/src/test/java/com/scalar/dl/client/tool/ContractsListingTest.java +++ b/client/src/test/java/com/scalar/dl/client/tool/ContractsListingTest.java @@ -54,7 +54,6 @@ void returns0AsExitCode() throws Exception { }; ContractsListing command = parseArgs(args); // Mock service that returns ContractExecutionResult. - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); JsonObject jsonObject = Json.createObjectBuilder() @@ -64,7 +63,7 @@ void returns0AsExitCode() throws Exception { when(serviceMock.listContracts(eq("CONTRACT_ID"))).thenReturn(jsonObject); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -147,12 +146,13 @@ public void createClientServiceWithClientConfig(@TempDir Path tempDir) throws Ex class whereClientExceptionIsThrownByClientService { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), // Set the optional options. "--contract-id=CONTRACT_ID", }; @@ -160,14 +160,16 @@ void returns1AsExitCode() { // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); when(serviceMock.listContracts(eq("CONTRACT_ID"))) .thenThrow(new ClientException("", StatusCode.RUNTIME_ERROR)); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); } } } diff --git a/client/src/test/java/com/scalar/dl/client/tool/ContractsRegistrationTest.java b/client/src/test/java/com/scalar/dl/client/tool/ContractsRegistrationTest.java index 0a10285e..8ff0f70c 100644 --- a/client/src/test/java/com/scalar/dl/client/tool/ContractsRegistrationTest.java +++ b/client/src/test/java/com/scalar/dl/client/tool/ContractsRegistrationTest.java @@ -83,14 +83,14 @@ void returns0AsExitCode() throws Exception { String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", "--contracts-file=CONTRACTS_FILE", + "--properties=PROPERTIES_FILE", + "--contracts-file=" + contractsFile.getAbsolutePath(), }; ContractsRegistration command = parseArgs(args); - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); // Act - int exitCode = command.call(factoryMock, serviceMock, contractsFile); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -126,10 +126,10 @@ void returns0AsExitCode() throws Exception { String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", "--contracts-file=CONTRACTS_FILE", + "--properties=PROPERTIES_FILE", + "--contracts-file=" + contractsFile.getAbsolutePath(), }; ContractsRegistration command = parseArgs(args); - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); // Mock that one of the contracts is already registered. doThrow(new ClientException("", StatusCode.CONTRACT_ALREADY_REGISTERED)) @@ -141,7 +141,7 @@ void returns0AsExitCode() throws Exception { any(JsonNode.class)); // Act - int exitCode = command.call(factoryMock, serviceMock, contractsFile); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -183,7 +183,7 @@ public void createClientServiceWithGatewayClientConfig(@TempDir Path tempDir) new String[] { // Set the required options. propertiesOption, - "--contracts-file=CONTRACTS_FILE", + "--contracts-file=" + contractsFile.getAbsolutePath(), // Enable Gateway. "--use-gateway" }; @@ -192,7 +192,7 @@ public void createClientServiceWithGatewayClientConfig(@TempDir Path tempDir) doReturn(mock(ClientService.class)).when(factory).create(any(GatewayClientConfig.class)); // Act - command.call(factory, contractsFile); + command.call(factory); // Verify verify(factory).create(any(GatewayClientConfig.class)); @@ -212,7 +212,7 @@ public void createClientServiceWithClientConfig(@TempDir Path tempDir) throws Ex String[] args = new String[] { // Set the required options. - propertiesOption, "--contracts-file=CONTRACTS_FILE", + propertiesOption, "--contracts-file=" + contractsFile.getAbsolutePath(), // Gateway is disabled by default. }; ContractsRegistration command = parseArgs(args); @@ -220,7 +220,7 @@ public void createClientServiceWithClientConfig(@TempDir Path tempDir) throws Ex doReturn(mock(ClientService.class)).when(factory).create(any(ClientConfig.class)); // Act - command.call(factory, contractsFile); + command.call(factory); // Verify verify(factory).create(any(ClientConfig.class)); @@ -238,11 +238,11 @@ void returns1AsExitCode() throws Exception { String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", "--contracts-file=CONTRACTS_FILE", + "--properties=PROPERTIES_FILE", + "--contracts-file=" + contractsFile.getAbsolutePath(), }; ContractsRegistration command = parseArgs(args); // Mock service that throws an exception. - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); doThrow(new ClientException("", StatusCode.RUNTIME_ERROR)) .when(serviceMock) @@ -253,7 +253,7 @@ void returns1AsExitCode() throws Exception { any(JsonNode.class)); // Act - int exitCode = command.call(factoryMock, serviceMock, contractsFile); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(1); @@ -319,15 +319,15 @@ void returns1AsExitCode() throws Exception { String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", "--contracts-file=CONTRACTS_FILE", + "--properties=PROPERTIES_FILE", + "--contracts-file=" + malformedContractsFile.getAbsolutePath(), }; ContractsRegistration command = parseArgs(args); // Mock service that throws an exception. - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); // Act - int exitCode = command.call(factoryMock, serviceMock, malformedContractsFile); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(1); diff --git a/client/src/test/java/com/scalar/dl/client/tool/FunctionRegistrationTest.java b/client/src/test/java/com/scalar/dl/client/tool/FunctionRegistrationTest.java index a0c0cd7d..2e706b9f 100644 --- a/client/src/test/java/com/scalar/dl/client/tool/FunctionRegistrationTest.java +++ b/client/src/test/java/com/scalar/dl/client/tool/FunctionRegistrationTest.java @@ -33,7 +33,7 @@ void setup() { class call { @Test @DisplayName("returns 0 as exit code") - void returns0AsExitCode() { + void returns0AsExitCode() throws ClientException { // Arrange String[] args = new String[] { @@ -44,11 +44,10 @@ void returns0AsExitCode() { "--function-class-file=FUNCTION_CLASS_FILE", }; FunctionRegistration command = parseArgs(args); - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -127,12 +126,13 @@ public void createClientServiceWithClientConfig(@TempDir Path tempDir) throws Ex class whereClientExceptionIsThrownByClientService { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), "--function-id=FUNCTION_ID", "--function-binary-name=FUNCTION_BINARY_NAME", "--function-class-file=FUNCTION_CLASS_FILE", @@ -141,16 +141,18 @@ void returns1AsExitCode() { // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); doThrow(new ClientException("", StatusCode.RUNTIME_ERROR)) .when(serviceMock) .registerFunction( eq("FUNCTION_ID"), eq("FUNCTION_BINARY_NAME"), eq("FUNCTION_CLASS_FILE")); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); } } } diff --git a/client/src/test/java/com/scalar/dl/client/tool/FunctionsRegistrationTest.java b/client/src/test/java/com/scalar/dl/client/tool/FunctionsRegistrationTest.java index 0b524445..b8fbbb1c 100644 --- a/client/src/test/java/com/scalar/dl/client/tool/FunctionsRegistrationTest.java +++ b/client/src/test/java/com/scalar/dl/client/tool/FunctionsRegistrationTest.java @@ -80,14 +80,14 @@ void returns0AsExitCode() throws Exception { String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", "--functions-file=FUNCTIONS_FILE", + "--properties=PROPERTIES_FILE", + "--functions-file=" + functionsFile.getAbsolutePath(), }; FunctionsRegistration command = parseArgs(args); - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); // Act - int exitCode = command.call(factoryMock, serviceMock, functionsFile); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -121,7 +121,7 @@ public void createClientServiceWithGatewayClientConfig(@TempDir Path tempDir) new String[] { // Set the required options. propertiesOption, - "--functions-file=FUNCTIONS_FILE", + "--functions-file=" + functionsFile.getAbsolutePath(), // Enable Gateway. "--use-gateway" }; @@ -130,7 +130,7 @@ public void createClientServiceWithGatewayClientConfig(@TempDir Path tempDir) doReturn(mock(ClientService.class)).when(factory).create(any(GatewayClientConfig.class)); // Act - command.call(factory, functionsFile); + command.call(factory); // Verify verify(factory).create(any(GatewayClientConfig.class)); @@ -150,7 +150,7 @@ public void createClientServiceWithClientConfig(@TempDir Path tempDir) throws Ex String[] args = new String[] { // Set the required options. - propertiesOption, "--functions-file=FUNCTIONS_FILE", + propertiesOption, "--functions-file=" + functionsFile.getAbsolutePath(), // Gateway is disabled by default. }; FunctionsRegistration command = parseArgs(args); @@ -158,7 +158,7 @@ public void createClientServiceWithClientConfig(@TempDir Path tempDir) throws Ex doReturn(mock(ClientService.class)).when(factory).create(any(ClientConfig.class)); // Act - command.call(factory, functionsFile); + command.call(factory); // Verify verify(factory).create(any(ClientConfig.class)); @@ -176,11 +176,11 @@ void returns1AsExitCode() throws Exception { String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", "--functions-file=FUNCTIONS_FILE", + "--properties=PROPERTIES_FILE", + "--functions-file=" + functionsFile.getAbsolutePath(), }; FunctionsRegistration command = parseArgs(args); // Mock service that throws an exception. - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); doThrow(new ClientException("", StatusCode.RUNTIME_ERROR)) .when(serviceMock) @@ -188,7 +188,7 @@ void returns1AsExitCode() throws Exception { eq("FUNCTION_ID_1"), eq("FUNCTION_BINARY_NAME_1"), eq("FUNCTION_CLASS_FILE_1")); // Act - int exitCode = command.call(factoryMock, serviceMock, functionsFile); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(1); @@ -250,15 +250,15 @@ void returns1AsExitCode() throws Exception { String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", "--functions-file=FUNCTIONS_FILE", + "--properties=PROPERTIES_FILE", + "--functions-file=" + malformedFunctionsFile.getAbsolutePath(), }; FunctionsRegistration command = parseArgs(args); // Mock service that throws an exception. - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); // Act - int exitCode = command.call(factoryMock, serviceMock, malformedFunctionsFile); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(1); diff --git a/client/src/test/java/com/scalar/dl/client/tool/LedgerValidationTest.java b/client/src/test/java/com/scalar/dl/client/tool/LedgerValidationTest.java index f9debd10..252179eb 100644 --- a/client/src/test/java/com/scalar/dl/client/tool/LedgerValidationTest.java +++ b/client/src/test/java/com/scalar/dl/client/tool/LedgerValidationTest.java @@ -36,7 +36,7 @@ class call { class whereAgeIsSetInAssetIdOption { @Test @DisplayName("returns 0 as exit code") - void returns0AsExitCode() { + void returns0AsExitCode() throws ClientException { // Arrange String[] args = new String[] { @@ -47,7 +47,6 @@ void returns0AsExitCode() { }; LedgerValidation command = parseArgs(args); // Mock service that returns ContractExecutionResult. - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); LedgerValidationResult result1 = new LedgerValidationResult(StatusCode.OK, null, null); @@ -56,7 +55,7 @@ void returns0AsExitCode() { when(serviceMock.validateLedger("ASSET_ID_2", 2, 3)).thenReturn(result2); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -139,12 +138,13 @@ public void createClientServiceWithClientConfig(@TempDir Path tempDir) throws Ex class whereClientExceptionIsThrownByClientService { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), "--asset-id=ASSET_ID_1,0,1", // startAge = 0 & endAge = 1. "--asset-id=ASSET_ID_2,2,3", // startAge = 2 & endAge = 3. }; @@ -152,15 +152,17 @@ void returns1AsExitCode() { // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); doThrow(new ClientException("", StatusCode.RUNTIME_ERROR)) .when(serviceMock) .validateLedger("ASSET_ID_1", 0, 1); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); verify(serviceMock).validateLedger("ASSET_ID_1", 0, 1); verify(serviceMock, never()).validateLedger(eq("ASSET_ID_2"), anyInt(), anyInt()); @@ -172,12 +174,13 @@ void returns1AsExitCode() { class whenAgeIsNotInteger { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), "--asset-id=ASSET_ID_1,a,1", // Set invalid start age. "--asset-id=ASSET_ID_2,2,3", }; @@ -185,12 +188,14 @@ void returns1AsExitCode() { // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); // Verify that validate-ledger was not called. verify(serviceMock, never()).validateLedger(eq("ASSET_ID_1"), anyInt(), anyInt()); @@ -203,12 +208,13 @@ void returns1AsExitCode() { class whenEndAgeIsMissing { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), "--asset-id=ASSET_ID_1,0", // startAge = 0 & no endAge. "--asset-id=ASSET_ID_2,2,3", // startAge = 2 & endAge = 3. }; @@ -216,12 +222,14 @@ void returns1AsExitCode() { // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); // Verify validate-ledger was not called. verify(serviceMock, never()).validateLedger(eq("ASSET_ID_1"), anyInt(), anyInt()); @@ -234,12 +242,13 @@ void returns1AsExitCode() { class whenTooManyArgsAreSet { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), "--asset-id=ASSET_ID_1,0,1,2", // startAge = 0 & endAge = 1 (and more...). "--asset-id=ASSET_ID_2,2,3", // startAge = 2 & endAge = 3. }; @@ -247,12 +256,14 @@ void returns1AsExitCode() { // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); // Verify validate-ledger was not called. verify(serviceMock, never()).validateLedger(eq("ASSET_ID_1"), anyInt(), anyInt()); @@ -266,7 +277,7 @@ void returns1AsExitCode() { class whereAgeIsNotSetInAssetIdOption { @Test @DisplayName("returns 0 as exit code") - void returns0AsExitCode() { + void returns0AsExitCode() throws ClientException { // Arrange String[] args = new String[] { @@ -275,7 +286,6 @@ void returns0AsExitCode() { }; LedgerValidation command = parseArgs(args); // Mock service that returns ContractExecutionResult. - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); LedgerValidationResult result1 = new LedgerValidationResult(StatusCode.OK, null, null); @@ -284,7 +294,7 @@ void returns0AsExitCode() { when(serviceMock.validateLedger("ASSET_ID_2")).thenReturn(result2); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -298,26 +308,31 @@ void returns0AsExitCode() { class whereClientExceptionIsThrownByClientService { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", "--asset-id=ASSET_ID_1", "--asset-id=ASSET_ID_2", + "--properties=" + file.getAbsolutePath(), + "--asset-id=ASSET_ID_1", + "--asset-id=ASSET_ID_2", }; LedgerValidation command = parseArgs(args); // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); doThrow(new ClientException("", StatusCode.RUNTIME_ERROR)) .when(serviceMock) .validateLedger("ASSET_ID_1"); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); verify(serviceMock).validateLedger("ASSET_ID_1"); verify(serviceMock, never()).validateLedger("ASSET_ID_2"); diff --git a/client/src/test/java/com/scalar/dl/client/tool/SecretRegistrationTest.java b/client/src/test/java/com/scalar/dl/client/tool/SecretRegistrationTest.java index 5fc6b389..6a1417eb 100644 --- a/client/src/test/java/com/scalar/dl/client/tool/SecretRegistrationTest.java +++ b/client/src/test/java/com/scalar/dl/client/tool/SecretRegistrationTest.java @@ -8,6 +8,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.scalar.dl.client.config.ClientConfig; import com.scalar.dl.client.config.GatewayClientConfig; @@ -37,7 +38,7 @@ void setup() { class call { @Test @DisplayName("returns 0 as exit code") - void returns0AsExitCode() { + void returns0AsExitCode() throws ClientException { // Arrange String[] args = new String[] { @@ -46,11 +47,10 @@ void returns0AsExitCode() { }; SecretRegistration command = parseArgs(args); // Mock service that returns ContractExecutionResult. - ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); @@ -119,26 +119,29 @@ public void createClientServiceWithClientConfig(@TempDir Path tempDir) throws Ex class whereClientExceptionIsThrownByClientService { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { // Set the required options. - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), }; SecretRegistration command = parseArgs(args); // Mock service that throws an exception. ClientServiceFactory factoryMock = mock(ClientServiceFactory.class); ClientService serviceMock = mock(ClientService.class); + when(factoryMock.create(any(ClientConfig.class))).thenReturn(serviceMock); doThrow(new ClientException("", StatusCode.RUNTIME_ERROR)) .when(serviceMock) .registerSecret(); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); + verify(factoryMock).close(); } } } diff --git a/hash-store/src/main/java/com/scalar/dl/hashstore/client/tool/AbstractHashStoreCommand.java b/hash-store/src/main/java/com/scalar/dl/hashstore/client/tool/AbstractHashStoreCommand.java index 7cf259a3..c419ba07 100644 --- a/hash-store/src/main/java/com/scalar/dl/hashstore/client/tool/AbstractHashStoreCommand.java +++ b/hash-store/src/main/java/com/scalar/dl/hashstore/client/tool/AbstractHashStoreCommand.java @@ -14,7 +14,7 @@ public abstract class AbstractHashStoreCommand extends CommonOptions implements Callable { @Override - public Integer call() throws Exception { + public final Integer call() throws Exception { return call(new HashStoreClientServiceFactory()); } diff --git a/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/AbstractTableStoreCommand.java b/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/AbstractTableStoreCommand.java new file mode 100644 index 00000000..2927ec64 --- /dev/null +++ b/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/AbstractTableStoreCommand.java @@ -0,0 +1,46 @@ +package com.scalar.dl.tablestore.client.tool; + +import com.google.common.annotations.VisibleForTesting; +import com.scalar.dl.client.config.ClientConfig; +import com.scalar.dl.client.config.GatewayClientConfig; +import com.scalar.dl.client.exception.ClientException; +import com.scalar.dl.client.tool.Common; +import com.scalar.dl.client.tool.CommonOptions; +import com.scalar.dl.tablestore.client.service.TableStoreClientService; +import com.scalar.dl.tablestore.client.service.TableStoreClientServiceFactory; +import java.io.File; +import java.util.concurrent.Callable; + +public abstract class AbstractTableStoreCommand extends CommonOptions implements Callable { + + @Override + public final Integer call() throws Exception { + return call(new TableStoreClientServiceFactory()); + } + + @VisibleForTesting + public final Integer call(TableStoreClientServiceFactory factory) throws Exception { + try { + TableStoreClientService service = + useGateway + ? factory.create(new GatewayClientConfig(new File(properties)), false) + : factory.create(new ClientConfig(new File(properties)), false); + return execute(service); + } catch (ClientException e) { + Common.printError(e); + printStackTrace(e); + return 1; + } finally { + factory.close(); + } + } + + /** + * Executes the specific command logic. + * + * @param service the client service to use for execution. + * @return the exit code. + * @throws ClientException if the execution fails. + */ + protected abstract Integer execute(TableStoreClientService service) throws ClientException; +} diff --git a/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/Bootstrap.java b/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/Bootstrap.java index 69341447..85b9b3b9 100644 --- a/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/Bootstrap.java +++ b/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/Bootstrap.java @@ -1,48 +1,19 @@ package com.scalar.dl.tablestore.client.tool; -import com.google.common.annotations.VisibleForTesting; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.tool.Common; -import com.scalar.dl.client.tool.CommonOptions; import com.scalar.dl.tablestore.client.service.TableStoreClientService; -import com.scalar.dl.tablestore.client.service.TableStoreClientServiceFactory; -import java.io.File; -import java.util.concurrent.Callable; import picocli.CommandLine.Command; @Command( name = "bootstrap", description = "Bootstrap the table store by registering identity and contracts.") -public class Bootstrap extends CommonOptions implements Callable { +public class Bootstrap extends AbstractTableStoreCommand { @Override - public Integer call() throws Exception { - return call(new TableStoreClientServiceFactory()); - } - - @VisibleForTesting - Integer call(TableStoreClientServiceFactory factory) throws Exception { - TableStoreClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties)), false) - : factory.create(new ClientConfig(new File(properties)), false); - return call(factory, service); - } - - @VisibleForTesting - Integer call(TableStoreClientServiceFactory factory, TableStoreClientService service) { - try { - service.bootstrap(); - Common.printOutput(null); - return 0; - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - return 1; - } finally { - factory.close(); - } + protected Integer execute(TableStoreClientService service) throws ClientException { + service.bootstrap(); + Common.printOutput(null); + return 0; } } diff --git a/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/LedgerValidation.java b/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/LedgerValidation.java index 1de77c1c..a81745fb 100644 --- a/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/LedgerValidation.java +++ b/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/LedgerValidation.java @@ -1,22 +1,15 @@ package com.scalar.dl.tablestore.client.tool; -import com.google.common.annotations.VisibleForTesting; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.tool.Common; -import com.scalar.dl.client.tool.CommonOptions; import com.scalar.dl.ledger.model.LedgerValidationResult; import com.scalar.dl.tablestore.client.service.TableStoreClientService; -import com.scalar.dl.tablestore.client.service.TableStoreClientServiceFactory; -import java.io.File; -import java.util.concurrent.Callable; import picocli.CommandLine; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; @Command(name = "validate-ledger", description = "Validate a specified asset in the ledger.") -public class LedgerValidation extends CommonOptions implements Callable { +public class LedgerValidation extends AbstractTableStoreCommand { @CommandLine.Option( names = {"--start-age"}, @@ -69,41 +62,19 @@ static class ColumnName { } @Override - public Integer call() throws Exception { - return call(new TableStoreClientServiceFactory()); - } - - @VisibleForTesting - Integer call(TableStoreClientServiceFactory factory) throws Exception { - TableStoreClientService service = - useGateway - ? factory.create(new GatewayClientConfig(new File(properties)), false) - : factory.create(new ClientConfig(new File(properties)), false); - return call(factory, service); - } - - @VisibleForTesting - Integer call(TableStoreClientServiceFactory factory, TableStoreClientService service) { - try { - LedgerValidationResult result; - if (key == null) { - result = service.validateTableSchema(tableName, startAge, endAge); + protected Integer execute(TableStoreClientService service) throws ClientException { + LedgerValidationResult result; + if (key == null) { + result = service.validateTableSchema(tableName, startAge, endAge); + } else { + if (key.name.primary != null) { + result = service.validateRecord(tableName, key.name.primary, key.value, startAge, endAge); } else { - if (key.name.primary != null) { - result = service.validateRecord(tableName, key.name.primary, key.value, startAge, endAge); - } else { - result = - service.validateIndexRecord(tableName, key.name.index, key.value, startAge, endAge); - } + result = + service.validateIndexRecord(tableName, key.name.index, key.value, startAge, endAge); } - Common.printJson(Common.getValidationResult(result)); - return 0; - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - return 1; - } finally { - factory.close(); } + Common.printJson(Common.getValidationResult(result)); + return 0; } } diff --git a/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/StatementExecution.java b/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/StatementExecution.java index 52fe9827..687b94bd 100644 --- a/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/StatementExecution.java +++ b/table-store/src/main/java/com/scalar/dl/tablestore/client/tool/StatementExecution.java @@ -1,23 +1,16 @@ package com.scalar.dl.tablestore.client.tool; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.annotations.VisibleForTesting; -import com.scalar.dl.client.config.ClientConfig; -import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.tool.Common; -import com.scalar.dl.client.tool.CommonOptions; import com.scalar.dl.ledger.model.ExecutionResult; import com.scalar.dl.ledger.util.JacksonSerDe; import com.scalar.dl.tablestore.client.service.TableStoreClientService; -import com.scalar.dl.tablestore.client.service.TableStoreClientServiceFactory; -import java.io.File; -import java.util.concurrent.Callable; import picocli.CommandLine; import picocli.CommandLine.Command; @Command(name = "execute-statement", description = "Execute a specified statement.") -public class StatementExecution extends CommonOptions implements Callable { +public class StatementExecution extends AbstractTableStoreCommand { @CommandLine.Option( names = {"--statement"}, @@ -27,38 +20,16 @@ public class StatementExecution extends CommonOptions implements Callable { - System.out.println("Result:"); - Common.printJson(serde.deserialize(r)); - }); - return 0; - } catch (ClientException e) { - Common.printError(e); - printStackTrace(e); - return 1; - } finally { - factory.close(); - } + ExecutionResult result = service.executeStatement(statement); + result + .getResult() + .ifPresent( + r -> { + System.out.println("Result:"); + Common.printJson(serde.deserialize(r)); + }); + return 0; } } diff --git a/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/BootstrapTest.java b/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/BootstrapTest.java index 222423fd..122b24a1 100644 --- a/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/BootstrapTest.java +++ b/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/BootstrapTest.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.Mockito.*; +import com.scalar.dl.client.config.ClientConfig; import com.scalar.dl.client.config.GatewayClientConfig; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.tool.CommandLineTestUtils; @@ -14,7 +15,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.PrintStream; -import java.io.UnsupportedEncodingException; import java.nio.file.Path; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -50,16 +50,14 @@ void returns0AsExitCode() { "--properties=PROPERTIES_FILE", }; Bootstrap command = parseArgs(args); - TableStoreClientServiceFactory factoryMock = mock(TableStoreClientServiceFactory.class); TableStoreClientService serviceMock = mock(TableStoreClientService.class); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); verify(serviceMock).bootstrap(); - verify(factoryMock).close(); } } @@ -68,25 +66,27 @@ void returns0AsExitCode() { class whereBootstrapFailsViaTableStoreClientService { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() throws UnsupportedEncodingException { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = new String[] { - "--properties=PROPERTIES_FILE", + "--properties=" + file.getAbsolutePath(), }; Bootstrap command = parseArgs(args); TableStoreClientServiceFactory factoryMock = mock(TableStoreClientServiceFactory.class); TableStoreClientService serviceMock = mock(TableStoreClientService.class); + + when(factoryMock.create(any(ClientConfig.class), anyBoolean())).thenReturn(serviceMock); doThrow(new ClientException("Some error", StatusCode.RUNTIME_ERROR)) .when(serviceMock) .bootstrap(); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); - verify(serviceMock).bootstrap(); verify(factoryMock).close(); assertThat(outputStreamCaptor.toString(UTF_8.name())).contains("Some error"); } diff --git a/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/LedgerValidationTest.java b/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/LedgerValidationTest.java index 501be87c..a749fd25 100644 --- a/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/LedgerValidationTest.java +++ b/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/LedgerValidationTest.java @@ -37,7 +37,7 @@ class call { class whereTableNameIsSet { @Test @DisplayName("validates table schema when no key is specified and returns 0 as exit code") - void validatesTableSchemaAndReturns0AsExitCode() { + void validatesTableSchemaAndReturns0AsExitCode() throws ClientException { // Arrange String[] args = new String[] { @@ -47,24 +47,22 @@ void validatesTableSchemaAndReturns0AsExitCode() { "--end-age=10" }; LedgerValidation command = parseArgs(args); - TableStoreClientServiceFactory factoryMock = mock(TableStoreClientServiceFactory.class); TableStoreClientService serviceMock = mock(TableStoreClientService.class); LedgerValidationResult result = new LedgerValidationResult(StatusCode.OK, null, null); when(serviceMock.validateTableSchema("test_table", 0, 10)).thenReturn(result); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); verify(serviceMock).validateTableSchema("test_table", 0, 10); - verify(factoryMock).close(); } @Test @DisplayName("validates record with primary key and returns 0 as exit code") - void validatesRecordWithPrimaryKeyAndReturns0AsExitCode() { + void validatesRecordWithPrimaryKeyAndReturns0AsExitCode() throws ClientException { // Arrange String[] args = new String[] { @@ -76,24 +74,22 @@ void validatesRecordWithPrimaryKeyAndReturns0AsExitCode() { "--end-age=10" }; LedgerValidation command = parseArgs(args); - TableStoreClientServiceFactory factoryMock = mock(TableStoreClientServiceFactory.class); TableStoreClientService serviceMock = mock(TableStoreClientService.class); LedgerValidationResult result = new LedgerValidationResult(StatusCode.OK, null, null); when(serviceMock.validateRecord("test_table", "id", "\"123\"", 0, 10)).thenReturn(result); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); verify(serviceMock).validateRecord("test_table", "id", "\"123\"", 0, 10); - verify(factoryMock).close(); } @Test @DisplayName("validates index record and returns 0 as exit code") - void validatesIndexRecordAndReturns0AsExitCode() { + void validatesIndexRecordAndReturns0AsExitCode() throws ClientException { // Arrange String[] args = new String[] { @@ -105,7 +101,6 @@ void validatesIndexRecordAndReturns0AsExitCode() { "--end-age=10" }; LedgerValidation command = parseArgs(args); - TableStoreClientServiceFactory factoryMock = mock(TableStoreClientServiceFactory.class); TableStoreClientService serviceMock = mock(TableStoreClientService.class); LedgerValidationResult result = new LedgerValidationResult(StatusCode.OK, null, null); @@ -113,13 +108,12 @@ void validatesIndexRecordAndReturns0AsExitCode() { .thenReturn(result); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); verify(serviceMock) .validateIndexRecord("test_table", "email", "\"test@example.com\"", 0, 10); - verify(factoryMock).close(); } } @@ -158,18 +152,21 @@ public void createClientServiceWithGatewayClientConfig(@TempDir Path tempDir) class whereClientExceptionIsThrownByTableStoreClientService { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange - String[] args = new String[] {"--properties=PROPERTIES_FILE", "--table-name=test_table"}; + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); + String[] args = + new String[] {"--properties=" + file.getAbsolutePath(), "--table-name=test_table"}; LedgerValidation command = parseArgs(args); TableStoreClientServiceFactory factoryMock = mock(TableStoreClientServiceFactory.class); TableStoreClientService serviceMock = mock(TableStoreClientService.class); + when(factoryMock.create(any(ClientConfig.class), anyBoolean())).thenReturn(serviceMock); when(serviceMock.validateTableSchema(anyString(), anyInt(), anyInt())) .thenThrow(new ClientException("Validation failed", StatusCode.RUNTIME_ERROR)); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1); diff --git a/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/StatementExecutionTest.java b/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/StatementExecutionTest.java index a34095d5..c9f83832 100644 --- a/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/StatementExecutionTest.java +++ b/table-store/src/test/java/com/scalar/dl/tablestore/client/tool/StatementExecutionTest.java @@ -17,7 +17,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.PrintStream; -import java.io.UnsupportedEncodingException; import java.nio.file.Path; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; @@ -55,7 +54,6 @@ void returns0AsExitCodeWithResult() throws Exception { "--statement=INSERT INTO test_table VALUES {'id': '123', 'name': 'test'}" }; StatementExecution command = parseArgs(args); - TableStoreClientServiceFactory factoryMock = mock(TableStoreClientServiceFactory.class); TableStoreClientService serviceMock = mock(TableStoreClientService.class); String resultJson = "{\"status\":\"success\"}"; @@ -66,13 +64,12 @@ void returns0AsExitCodeWithResult() throws Exception { when(serviceMock.executeStatement(anyString())).thenReturn(result); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); verify(serviceMock) .executeStatement("INSERT INTO test_table VALUES {'id': '123', 'name': 'test'}"); - verify(factoryMock).close(); String stdout = outputStreamCaptor.toString(UTF_8.name()); assertThat(stdout).contains("Result:"); @@ -89,7 +86,6 @@ void returns0AsExitCodeWithoutResult() throws Exception { "--statement=CREATE TABLE test_table (id STRING PRIMARY KEY, name STRING)" }; StatementExecution command = parseArgs(args); - TableStoreClientServiceFactory factoryMock = mock(TableStoreClientServiceFactory.class); TableStoreClientService serviceMock = mock(TableStoreClientService.class); ContractExecutionResult contractResult = @@ -99,13 +95,12 @@ void returns0AsExitCodeWithoutResult() throws Exception { when(serviceMock.executeStatement(anyString())).thenReturn(result); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.execute(serviceMock); // Assert assertThat(exitCode).isEqualTo(0); verify(serviceMock) .executeStatement("CREATE TABLE test_table (id STRING PRIMARY KEY, name STRING)"); - verify(factoryMock).close(); String stdout = outputStreamCaptor.toString(UTF_8.name()); assertThat(stdout).doesNotContain("Result:"); @@ -152,19 +147,23 @@ public void createClientServiceWithGatewayClientConfig(@TempDir Path tempDir) class whereClientExceptionIsThrownByTableStoreClientService { @Test @DisplayName("returns 1 as exit code") - void returns1AsExitCode() throws UnsupportedEncodingException { + void returns1AsExitCode(@TempDir Path tempDir) throws Exception { // Arrange + File file = createDefaultClientPropertiesFile(tempDir, "client.props"); String[] args = - new String[] {"--properties=PROPERTIES_FILE", "--statement=INVALID STATEMENT"}; + new String[] { + "--properties=" + file.getAbsolutePath(), "--statement=INVALID STATEMENT" + }; StatementExecution command = parseArgs(args); TableStoreClientServiceFactory factoryMock = mock(TableStoreClientServiceFactory.class); TableStoreClientService serviceMock = mock(TableStoreClientService.class); + when(factoryMock.create(any(ClientConfig.class), anyBoolean())).thenReturn(serviceMock); when(serviceMock.executeStatement(anyString())) .thenThrow(new ClientException("Invalid statement", StatusCode.RUNTIME_ERROR)); // Act - int exitCode = command.call(factoryMock, serviceMock); + int exitCode = command.call(factoryMock); // Assert assertThat(exitCode).isEqualTo(1);