From 6432b7bf648aa60cba0db3edef1eda4c7b05a18d Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 8 Nov 2021 21:20:34 -0600 Subject: [PATCH] Initial code checkin --- .gitignore | 7 + checkstyle.xml | 242 +++++++++ pom.xml | 129 +++++ .../picocli/QPicoCliImplementation.java | 489 ++++++++++++++++++ .../picocli/QPicoCliImplementationTest.java | 308 +++++++++++ .../qqq/frontend/picocli/TestUtils.java | 94 ++++ src/test/resources/prime-test-database.sql | 18 + 7 files changed, 1287 insertions(+) create mode 100644 checkstyle.xml create mode 100644 pom.xml create mode 100644 src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java create mode 100644 src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java create mode 100644 src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java create mode 100644 src/test/resources/prime-test-database.sql diff --git a/.gitignore b/.gitignore index a1c2a238..39736a21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ +target/ +*.iml + + +############################################# +## Original contents from github template: ## +############################################# # Compiled class file *.class diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 00000000..fadf2cda --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..a3e1e473 --- /dev/null +++ b/pom.xml @@ -0,0 +1,129 @@ + + + 4.0.0 + + com.kingsrook.qqq + qqq-middleware-picocli + 0.0-SNAPSHOT + + + + + + + UTF-8 + UTF-8 + 17 + 17 + true + true + + + + + + com.kingsrook.qqq + qqq-backend-core + 0.0-SNAPSHOT + + + com.kingsrook.qqq + qqq-backend-module-rdbms + 0.0-SNAPSHOT + test + + + + + info.picocli + picocli + 4.6.1 + + + com.h2database + h2 + 1.4.197 + test + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + + org.apache.logging.log4j + log4j-api + 2.14.1 + + + org.apache.logging.log4j + log4j-core + 2.14.1 + + + org.junit.jupiter + junit-jupiter-engine + 5.8.1 + test + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + -Xlint:unchecked + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + + com.puppycrawl.tools + checkstyle + 9.0 + + + + + validate + validate + + checkstyle.xml + + UTF-8 + true + false + true + warning + **/target/generated-sources/*.* + + + + check + + + + + + + + diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java new file mode 100644 index 00000000..6017c827 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -0,0 +1,489 @@ +package com.kingsrook.qqq.frontend.picocli; + + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; +import com.kingsrook.qqq.backend.core.actions.InsertAction; +import com.kingsrook.qqq.backend.core.actions.MetaDataAction; +import com.kingsrook.qqq.backend.core.actions.QueryAction; +import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction; +import com.kingsrook.qqq.backend.core.adapters.CsvToQRecordAdapter; +import com.kingsrook.qqq.backend.core.adapters.JsonToQFieldMappingAdapter; +import com.kingsrook.qqq.backend.core.adapters.JsonToQRecordAdapter; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.AbstractQFieldMapping; +import com.kingsrook.qqq.backend.core.model.actions.InsertRequest; +import com.kingsrook.qqq.backend.core.model.actions.InsertResult; +import com.kingsrook.qqq.backend.core.model.actions.MetaDataRequest; +import com.kingsrook.qqq.backend.core.model.actions.MetaDataResult; +import com.kingsrook.qqq.backend.core.model.actions.QCriteriaOperator; +import com.kingsrook.qqq.backend.core.model.actions.QFilterCriteria; +import com.kingsrook.qqq.backend.core.model.actions.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.actions.QueryRequest; +import com.kingsrook.qqq.backend.core.model.actions.QueryResult; +import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataRequest; +import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataResult; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import picocli.CommandLine; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Model.OptionSpec; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.ParseResult; +import picocli.CommandLine.UnmatchedArgumentException; + + +/******************************************************************************* + ** Note: Please do not use System.out or .err here -- rather, use the CommandLine + ** object's out & err members - so the unit test can see the output! + * + *******************************************************************************/ +public class QPicoCliImplementation +{ + private static final Logger LOG = LogManager.getLogger(QPicoCliImplementation.class); + + public static final int DEFAULT_LIMIT = 20; + + private static QInstance qInstance; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static void main(String[] args) + { + QInstance qInstance = new QInstance(); + + // todo - parse args to look up metaData and prime instance + // todo - authentication + // qInstance.addBackend(QMetaDataProvider.getQBackend()); + + QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance); + int exitCode = qPicoCliImplementation.runCli("qapi", args); + System.exit(exitCode); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QPicoCliImplementation(QInstance qInstance) + { + QPicoCliImplementation.qInstance = qInstance; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public int runCli(String name, String[] args) + { + return (runCli(name, args, System.out, System.err)); + } + + + + /******************************************************************************* + ** examples - todo, make docs complete! + ** my-app-cli [--all] [--format=] + ** my-app-cli $table meta-data [--format=] + ** my-app-cli $table query [--filterId=]|[--filter=]|[--criteria=...] + ** my-app-cli $table get (--primaryKey=|--$uc=...) + ** my-app-cli $table delete (--primaryKey=|--$uc=...) + ** my-app-cli $table insert (--body=|--$field=...) + ** my-app-cli $table update (--primaryKey=|--$uc=...) (--body=|--$field=...) + ** + *******************************************************************************/ + public int runCli(String name, String[] args, PrintStream out, PrintStream err) + { + ////////////////////////////////// + // define the top-level command // + ////////////////////////////////// + CommandSpec topCommandSpec = CommandSpec.create(); + topCommandSpec.name(name); + topCommandSpec.version(name + " v1.0"); // todo... uh? + topCommandSpec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options + topCommandSpec.addOption(OptionSpec.builder("-m", "--meta-data") + .type(boolean.class) + .description("Output the meta-data for this CLI") + .build()); + + ///////////////////////////////////// + // add each table as a sub-command // + ///////////////////////////////////// + qInstance.getTables().keySet().stream().sorted().forEach(tableName -> + { + QTableMetaData table = qInstance.getTable(tableName); + + CommandSpec tableCommand = CommandSpec.create(); + topCommandSpec.addSubcommand(table.getName(), tableCommand); + + /////////////////////////////////////////////////// + // add table-specific sub-commands for the table // + /////////////////////////////////////////////////// + tableCommand.addSubcommand("meta-data", defineMetaDataCommand(table)); + tableCommand.addSubcommand("query", defineQueryCommand(table)); + tableCommand.addSubcommand("insert", defineInsertCommand(table)); + }); + + CommandLine commandLine = new CommandLine(topCommandSpec); + commandLine.setOut(new PrintWriter(out, true)); + commandLine.setErr(new PrintWriter(err, true)); + + try + { + ParseResult parseResult = commandLine.parseArgs(args); + + /////////////////////////////////////////// + // Did user request usage help (--help)? // + /////////////////////////////////////////// + if(commandLine.isUsageHelpRequested()) + { + commandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + //////////////////////////////////////////////// + // Did user request version help (--version)? // + //////////////////////////////////////////////// + else if(commandLine.isVersionHelpRequested()) + { + commandLine.printVersionHelp(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnVersionHelp(); + } + + /////////////////////////// + // else, run the command // + /////////////////////////// + return run(commandLine, parseResult); + } + catch(ParameterException ex) + { + ////////////////////////////////////////////////// + // handle command-line/param parsing exceptions // + ////////////////////////////////////////////////// + commandLine.getErr().println(ex.getMessage()); + UnmatchedArgumentException.printSuggestions(ex, commandLine.getErr()); + ex.getCommandLine().usage(commandLine.getErr()); + return commandLine.getCommandSpec().exitCodeOnInvalidInput(); + } + catch(Exception ex) + { + /////////////////////////////////////////// + // handle exceptions from business logic // + /////////////////////////////////////////// + commandLine.getErr().println("Error: " + ex.getMessage()); + return (commandLine.getCommandSpec().exitCodeOnExecutionException()); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private CommandSpec defineMetaDataCommand(QTableMetaData table) + { + return CommandSpec.create(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private CommandSpec defineQueryCommand(QTableMetaData table) + { + CommandSpec queryCommand = CommandSpec.create(); + queryCommand.addOption(OptionSpec.builder("-l", "--limit") + .type(int.class) + .build()); + queryCommand.addOption(OptionSpec.builder("-s", "--skip") + .type(int.class) + .build()); + queryCommand.addOption(OptionSpec.builder("-c", "--criteria") + .type(String[].class) + .build()); + + // todo - add the fields as explicit params? + + return queryCommand; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @SuppressWarnings("checkstyle:Indentation") + private CommandSpec defineInsertCommand(QTableMetaData table) + { + CommandSpec insertCommand = CommandSpec.create(); + + insertCommand.addOption(OptionSpec.builder("--jsonBody") + .type(String.class) + .build()); + + insertCommand.addOption(OptionSpec.builder("--jsonFile") + .type(String.class) + .build()); + + insertCommand.addOption(OptionSpec.builder("--csvFile") + .type(String.class) + .build()); + + insertCommand.addOption(OptionSpec.builder("--mapping") + .type(String.class) + .build()); + + for(QFieldMetaData field : table.getFields().values()) + { + insertCommand.addOption(OptionSpec.builder("--field-" + field.getName()) + .type( + switch(field.getType()) + { + case STRING, TEXT, HTML, PASSWORD -> String.class; + case INTEGER -> Integer.class; + case DECIMAL -> BigDecimal.class; + case DATE -> LocalDate.class; + case TIME -> LocalTime.class; + case DATE_TIME -> LocalDateTime.class; + default -> throw new IllegalStateException("Unsupported field type: " + field.getType()); + } + ) + .build()); + } + return insertCommand; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int run(CommandLine commandLine, ParseResult parseResult) throws QException + { + if(!parseResult.hasSubcommand()) + { + return runTopLevelCommand(commandLine, parseResult); + } + else + { + String subCommandName = parseResult.subcommand().commandSpec().name(); + CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName); + return runTableLevelCommand(subCommandLine, parseResult.subcommand()); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableLevelCommand(CommandLine commandLine, ParseResult tableParseResult) throws QException + { + String tableName = tableParseResult.commandSpec().name(); + + if(tableParseResult.hasSubcommand()) + { + ParseResult subParseResult = tableParseResult.subcommand(); + switch(subParseResult.commandSpec().name()) + { + case "meta-data": + { + return runTableMetaData(commandLine, tableName, subParseResult); + } + case "query": + { + return runTableQuery(commandLine, tableName, subParseResult); + } + case "insert": + { + return runTableInsert(commandLine, tableName, subParseResult); + } + default: + { + commandLine.getErr().println("Unknown command: " + subParseResult.commandSpec().name()); + commandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + } + } + else + { + commandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableMetaData(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException + { + TableMetaDataRequest tableMetaDataRequest = new TableMetaDataRequest(qInstance); + tableMetaDataRequest.setTableName(tableName); + TableMetaDataAction tableMetaDataAction = new TableMetaDataAction(); + TableMetaDataResult tableMetaDataResult = tableMetaDataAction.execute(tableMetaDataRequest); + commandLine.getOut().println(JsonUtils.toPrettyJson(tableMetaDataResult)); + return commandLine.getCommandSpec().exitCodeOnSuccess(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableQuery(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException + { + QueryRequest queryRequest = new QueryRequest(qInstance); + queryRequest.setTableName(tableName); + queryRequest.setSkip(subParseResult.matchedOptionValue("skip", null)); + queryRequest.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_LIMIT)); + + QQueryFilter filter = new QQueryFilter(); + queryRequest.setFilter(filter); + + String[] criteria = subParseResult.matchedOptionValue("criteria", new String[] {}); + for(String criterion : criteria) + { + // todo - parse! + String[] parts = criterion.split(" "); + QFilterCriteria qQueryCriteria = new QFilterCriteria(); + qQueryCriteria.setFieldName(parts[0]); + qQueryCriteria.setOperator(QCriteriaOperator.valueOf(parts[1])); + qQueryCriteria.setValues(List.of(parts[2])); + filter.addCriteria(qQueryCriteria); + } + + QueryAction queryAction = new QueryAction(); + QueryResult queryResult = queryAction.execute(queryRequest); + commandLine.getOut().println(JsonUtils.toPrettyJson(queryResult)); + return commandLine.getCommandSpec().exitCodeOnSuccess(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableInsert(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException + { + InsertRequest insertRequest = new InsertRequest(qInstance); + insertRequest.setTableName(tableName); + QTableMetaData table = qInstance.getTable(tableName); + + AbstractQFieldMapping mapping = null; + + if(subParseResult.hasMatchedOption("--mapping")) + { + String json = subParseResult.matchedOptionValue("--mapping", ""); + mapping = new JsonToQFieldMappingAdapter().buildMappingFromJson(json); + } + + ///////////////////////////////////////////// + // get the records that the user specified // + ///////////////////////////////////////////// + List recordList = null; + if(subParseResult.hasMatchedOption("--jsonBody")) + { + String json = subParseResult.matchedOptionValue("--jsonBody", ""); + recordList = new JsonToQRecordAdapter().buildRecordsFromJson(json, table, mapping); + } + else if(subParseResult.hasMatchedOption("--jsonFile")) + { + try + { + String path = subParseResult.matchedOptionValue("--jsonFile", ""); + String json = FileUtils.readFileToString(new File(path)); + recordList = new JsonToQRecordAdapter().buildRecordsFromJson(json, table, mapping); + } + catch(IOException e) + { + throw (new QException("Error building records from file:" + e.getMessage(), e)); + } + } + else if(subParseResult.hasMatchedOption("--csvFile")) + { + try + { + String path = subParseResult.matchedOptionValue("--csvFile", ""); + String csv = FileUtils.readFileToString(new File(path)); + recordList = new CsvToQRecordAdapter().buildRecordsFromCsv(csv, table, mapping); + } + catch(IOException e) + { + throw (new QException("Error building records from file:" + e.getMessage(), e)); + } + } + else + { + QRecord record = new QRecord(); + recordList = new ArrayList<>(); + recordList.add(record); + + boolean anyFields = false; + for(OptionSpec matchedOption : subParseResult.matchedOptions()) + { + if(matchedOption.longestName().startsWith("--field-")) + { + anyFields = true; + String fieldName = matchedOption.longestName().substring(8); + record.setValue(fieldName, matchedOption.getValue()); + } + } + + if(!anyFields) + { + CommandLine subCommandLine = commandLine.getSubcommands().get("insert"); + subCommandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + } + + insertRequest.setRecords(recordList); + + InsertAction insertAction = new InsertAction(); + InsertResult insertResult = insertAction.execute(insertRequest); + commandLine.getOut().println(JsonUtils.toPrettyJson(insertResult)); + return commandLine.getCommandSpec().exitCodeOnSuccess(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int runTopLevelCommand(CommandLine commandLine, ParseResult parseResult) throws QException + { + if(parseResult.hasMatchedOption("--meta-data")) + { + MetaDataRequest metaDataRequest = new MetaDataRequest(qInstance); + MetaDataAction metaDataAction = new MetaDataAction(); + MetaDataResult metaDataResult = metaDataAction.execute(metaDataRequest); + commandLine.getOut().println(JsonUtils.toPrettyJson(metaDataResult)); + return commandLine.getCommandSpec().exitCodeOnSuccess(); + } + + commandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } +} diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java new file mode 100644 index 00000000..5d132429 --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -0,0 +1,308 @@ +package com.kingsrook.qqq.frontend.picocli; + + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.backend.core.utils.StringUtils; +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/******************************************************************************* + ** + *******************************************************************************/ +class QPicoCliImplementationTest +{ + private static final boolean VERBOSE = true; + private static final String CLI_NAME = "cli-unit-test"; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @BeforeEach + public void beforeEach() throws Exception + { + TestUtils.primeTestDatabase(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_noArgs() + { + TestOutput testOutput = testCli(); + assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_help() + { + TestOutput testOutput = testCli("--help"); + assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME)); + assertTrue(testOutput.getOutput().matches("(?s).*Commands:.*person.*")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_version() + { + TestOutput testOutput = testCli("--version"); + assertTrue(testOutput.getOutput().contains(CLI_NAME + " v1.0")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_badOption() + { + String badOption = "--asdf"; + TestOutput testOutput = testCli(badOption); + assertTrue(testOutput.getError().contains("Unknown option: '" + badOption + "'")); + assertTrue(testOutput.getError().contains("Usage: " + CLI_NAME)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_metaData() + { + TestOutput testOutput = testCli("--meta-data"); + JSONObject metaData = JsonUtils.toJSONObject(testOutput.getOutput()); + assertNotNull(metaData); + assertEquals(1, metaData.keySet().size(), "Number of top-level keys"); + assertTrue(metaData.has("tables")); + JSONObject tables = metaData.getJSONObject("tables"); + JSONObject personTable = tables.getJSONObject("person"); + assertEquals("person", personTable.getString("name")); + assertEquals("Person", personTable.getString("label")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_table() + { + TestOutput testOutput = testCli("person"); + assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME + " person [COMMAND]")); + assertTrue(testOutput.getOutput().matches("(?s).*Commands:.*query.*")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_tableMetaData() + { + TestOutput testOutput = testCli("person", "meta-data"); + JSONObject metaData = JsonUtils.toJSONObject(testOutput.getOutput()); + assertNotNull(metaData); + assertEquals(1, metaData.keySet().size(), "Number of top-level keys"); + JSONObject table = metaData.getJSONObject("table"); + assertEquals(4, table.keySet().size(), "Number of mid-level keys"); + assertEquals("person", table.getString("name")); + assertEquals("Person", table.getString("label")); + assertEquals("id", table.getString("primaryKeyField")); + JSONObject fields = table.getJSONObject("fields"); + JSONObject field0 = fields.getJSONObject("id"); + assertEquals("id", field0.getString("name")); + assertEquals("INTEGER", field0.getString("type")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_tableQuery() + { + TestOutput testOutput = testCli("person", "query", "--skip=1", "--limit=2", "--criteria", "id NOT_EQUALS 3"); + JSONObject queryResult = JsonUtils.toJSONObject(testOutput.getOutput()); + assertNotNull(queryResult); + assertEquals(2, queryResult.getJSONArray("records").length()); + // query for id != 3, and skipping 1, expect to get back rows 2 & 4 + assertEquals(2, queryResult.getJSONArray("records").getJSONObject(0).getInt("primaryKey")); + assertEquals(4, queryResult.getJSONArray("records").getJSONObject(1).getInt("primaryKey")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private TestOutput testCli(String... args) + { + QInstance qInstance = TestUtils.defineInstance(); + QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); + + if(VERBOSE) + { + System.out.println("> " + CLI_NAME + (args == null ? "" : " " + StringUtils.join(" ", Arrays.stream(args).toList()))); + } + + qPicoCliImplementation.runCli(CLI_NAME, args, new PrintStream(outputStream, true), new PrintStream(errorStream, true)); + + String output = outputStream.toString(StandardCharsets.UTF_8); + String error = errorStream.toString(StandardCharsets.UTF_8); + + if(VERBOSE) + { + System.out.println(output); + System.err.println(error); + } + + TestOutput testOutput = new TestOutput(output, error); + return (testOutput); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private static class TestOutput + { + private String output; + private String[] outputLines; + private String error; + private String[] errorLines; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public TestOutput(String output, String error) + { + this.output = output; + this.error = error; + + this.outputLines = output.split("\n"); + this.errorLines = error.split("\n"); + } + + + + /******************************************************************************* + ** Getter for output + ** + *******************************************************************************/ + public String getOutput() + { + return output; + } + + + + /******************************************************************************* + ** Setter for output + ** + *******************************************************************************/ + public void setOutput(String output) + { + this.output = output; + } + + + + /******************************************************************************* + ** Getter for outputLines + ** + *******************************************************************************/ + public String[] getOutputLines() + { + return outputLines; + } + + + + /******************************************************************************* + ** Setter for outputLines + ** + *******************************************************************************/ + public void setOutputLines(String[] outputLines) + { + this.outputLines = outputLines; + } + + + + /******************************************************************************* + ** Getter for error + ** + *******************************************************************************/ + public String getError() + { + return error; + } + + + + /******************************************************************************* + ** Setter for error + ** + *******************************************************************************/ + public void setError(String error) + { + this.error = error; + } + + + + /******************************************************************************* + ** Getter for errorLines + ** + *******************************************************************************/ + public String[] getErrorLines() + { + return errorLines; + } + + + + /******************************************************************************* + ** Setter for errorLines + ** + *******************************************************************************/ + public void setErrorLines(String[] errorLines) + { + this.errorLines = errorLines; + } + } +} \ No newline at end of file diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java new file mode 100644 index 00000000..622d435a --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -0,0 +1,94 @@ +package com.kingsrook.qqq.frontend.picocli; + + +import java.io.InputStream; +import java.sql.Connection; +import java.util.List; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; +import com.kingsrook.qqq.backend.module.rdbms.RDBSMBackendMetaData; +import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; +import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; +import org.apache.commons.io.IOUtils; +import static junit.framework.Assert.assertNotNull; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TestUtils +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @SuppressWarnings("unchecked") + public static void primeTestDatabase() throws Exception + { + ConnectionManager connectionManager = new ConnectionManager(); + Connection connection = connectionManager.getConnection(new RDBSMBackendMetaData(TestUtils.defineBackend())); + InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql"); + assertNotNull(primeTestDatabaseSqlStream); + List lines = (List) IOUtils.readLines(primeTestDatabaseSqlStream); + String joinedSQL = String.join("\n", lines); + for(String sql : joinedSQL.split(";")) + { + QueryManager.executeUpdate(connection, sql); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static QInstance defineInstance() + { + QInstance qInstance = new QInstance(); + qInstance.addBackend(defineBackend()); + qInstance.addTable(defineTablePerson()); + return (qInstance); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static QBackendMetaData defineBackend() + { + return new QBackendMetaData() + .withName("default") + .withType("rdbms") + .withValue("vendor", "h2") + .withValue("hostName", "mem") + .withValue("databaseName", "test_database") + .withValue("username", "sa") + .withValue("password", ""); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static QTableMetaData defineTablePerson() + { + return new QTableMetaData() + .withName("person") + .withLabel("Person") + .withBackendName(defineBackend().getName()) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date")) + .withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date")) + .withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name")) + .withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name")) + .withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date")) + .withField(new QFieldMetaData("email", QFieldType.STRING)); + } + +} diff --git a/src/test/resources/prime-test-database.sql b/src/test/resources/prime-test-database.sql new file mode 100644 index 00000000..e5adab34 --- /dev/null +++ b/src/test/resources/prime-test-database.sql @@ -0,0 +1,18 @@ +DROP TABLE IF EXISTS person; +CREATE TABLE person +( + id SERIAL, + create_date TIMESTAMP DEFAULT now(), + modify_date TIMESTAMP DEFAULT now(), + + first_name VARCHAR(80) NOT NULL, + last_name VARCHAR(80) NOT NULL, + birth_date DATE, + email VARCHAR(250) NOT NULL +); + +INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com'); +INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com'); +INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com'); +INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (4, 'Tyler', 'Samples', '1990-01-01', 'tsamples@mmltholdings.com'); +INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com');