mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Merge pull request #19 from Kingsrook/feature/sprint-7-integration
Feature/sprint 7 integration
This commit is contained in:
@ -24,6 +24,8 @@ commands:
|
||||
name: Run Maven
|
||||
command: |
|
||||
mvn -s .circleci/mvn-settings.xml << parameters.maven_subcommand >>
|
||||
- store_artifacts:
|
||||
path: target/site/jacoco
|
||||
- run:
|
||||
name: Save test results
|
||||
command: |
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
target/
|
||||
*.iml
|
||||
.env
|
||||
|
||||
|
||||
#############################################
|
||||
@ -28,3 +29,4 @@ target/
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
.DS_Store
|
||||
|
16
pom.xml
16
pom.xml
@ -53,21 +53,27 @@
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-backend-core</artifactId>
|
||||
<version>0.2.0-SNAPSHOT</version>
|
||||
<version>0.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-backend-module-rdbms</artifactId>
|
||||
<version>0.2.0-SNAPSHOT</version>
|
||||
<version>0.2.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 3rd party deps specifically for this module -->
|
||||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
<version>4.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli-shell-jline3</artifactId>
|
||||
<version>4.6.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
@ -97,6 +103,12 @@
|
||||
<version>5.8.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.23.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -90,11 +90,13 @@ public class QCommandBuilder
|
||||
// add table-specific sub-commands for the table //
|
||||
///////////////////////////////////////////////////
|
||||
tableCommand.addSubcommand("meta-data", defineMetaDataCommand(table));
|
||||
tableCommand.addSubcommand("count", defineQueryCommand(table));
|
||||
tableCommand.addSubcommand("count", defineCountCommand(table));
|
||||
tableCommand.addSubcommand("get", defineGetCommand(table));
|
||||
tableCommand.addSubcommand("query", defineQueryCommand(table));
|
||||
tableCommand.addSubcommand("insert", defineInsertCommand(table));
|
||||
tableCommand.addSubcommand("update", defineUpdateCommand(table));
|
||||
tableCommand.addSubcommand("delete", defineDeleteCommand(table));
|
||||
tableCommand.addSubcommand("export", defineExportCommand(table));
|
||||
|
||||
List<QProcessMetaData> processes = qInstance.getProcessesForTable(tableName);
|
||||
if(CollectionUtils.nullSafeHasContents(processes))
|
||||
@ -158,6 +160,98 @@ public class QCommandBuilder
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private CommandLine.Model.CommandSpec defineExportCommand(QTableMetaData table)
|
||||
{
|
||||
CommandLine.Model.CommandSpec exportCommand = CommandLine.Model.CommandSpec.create();
|
||||
exportCommand.addOption(CommandLine.Model.OptionSpec.builder("-f", "--filename")
|
||||
.type(String.class)
|
||||
.description("File name (including path) to write to. File extension will be used to determine the report format. Supported formats are: csv, xlsx.")
|
||||
.required(true)
|
||||
.build());
|
||||
exportCommand.addOption(CommandLine.Model.OptionSpec.builder("-e", "--fieldNames")
|
||||
.type(String.class)
|
||||
.description("Comma-separated list of field names (e.g., from table meta-data) to include in the export. If not given, then all fields in the table are included.")
|
||||
.build());
|
||||
exportCommand.addOption(CommandLine.Model.OptionSpec.builder("-l", "--limit")
|
||||
.type(int.class)
|
||||
.description("Optional limit on the max number of records to include in the export.")
|
||||
.build());
|
||||
addCriteriaOption(exportCommand);
|
||||
|
||||
// todo - add the fields as explicit params?
|
||||
|
||||
return exportCommand;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** add the standard '--criteria' option
|
||||
*******************************************************************************/
|
||||
private void addCriteriaOption(CommandLine.Model.CommandSpec commandSpec)
|
||||
{
|
||||
commandSpec.addOption(CommandLine.Model.OptionSpec.builder("-c", "--criteria")
|
||||
.type(String[].class)
|
||||
.description("""
|
||||
Query filter criteria. May be given multiple times.
|
||||
Use format: "$fieldName $operator $value".
|
||||
e.g., "id EQUALS 42\"""")
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void addPrimaryKeyOrKeysOption(CommandLine.Model.CommandSpec updateCommand, String verbForDescription)
|
||||
{
|
||||
updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--primaryKey")
|
||||
// type(getClassForField(primaryKeyField))
|
||||
.type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type?
|
||||
.description("""
|
||||
Primary Key(s) for the records to %s.
|
||||
May provide multiple values, separated by commas""".formatted(verbForDescription))
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private CommandLine.Model.CommandSpec defineGetCommand(QTableMetaData table)
|
||||
{
|
||||
CommandLine.Model.CommandSpec getCommand = CommandLine.Model.CommandSpec.create();
|
||||
getCommand.addPositional(CommandLine.Model.PositionalParamSpec.builder()
|
||||
.index("0")
|
||||
// .type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type?
|
||||
.description("Primary key value from the table")
|
||||
.build());
|
||||
|
||||
return getCommand;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private CommandLine.Model.CommandSpec defineCountCommand(QTableMetaData table)
|
||||
{
|
||||
CommandLine.Model.CommandSpec countCommand = CommandLine.Model.CommandSpec.create();
|
||||
addCriteriaOption(countCommand);
|
||||
|
||||
// todo - add the fields as explicit params?
|
||||
|
||||
return countCommand;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -166,6 +260,7 @@ public class QCommandBuilder
|
||||
CommandLine.Model.CommandSpec updateCommand = CommandLine.Model.CommandSpec.create();
|
||||
|
||||
/*
|
||||
todo - future may accept files, similar to (bulk) insert
|
||||
updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--jsonBody")
|
||||
.type(String.class)
|
||||
.build());
|
||||
@ -183,11 +278,12 @@ public class QCommandBuilder
|
||||
.build());
|
||||
*/
|
||||
|
||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--primaryKey")
|
||||
// type(getClassForField(primaryKeyField))
|
||||
.type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type?
|
||||
.build());
|
||||
QFieldMetaData primaryKeyField = null;
|
||||
if(table.getPrimaryKeyField() != null)
|
||||
{
|
||||
primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
addPrimaryKeyOrKeysOption(updateCommand, "update");
|
||||
}
|
||||
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
@ -195,9 +291,14 @@ public class QCommandBuilder
|
||||
{
|
||||
updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--field-" + field.getName())
|
||||
.type(getClassForField(field))
|
||||
.description("""
|
||||
Value to set for the field %s""".formatted(field.getName()))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
addCriteriaOption(updateCommand);
|
||||
|
||||
return updateCommand;
|
||||
}
|
||||
|
||||
@ -248,6 +349,8 @@ public class QCommandBuilder
|
||||
.type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type?
|
||||
.build());
|
||||
|
||||
addCriteriaOption(deleteCommand);
|
||||
|
||||
return deleteCommand;
|
||||
}
|
||||
|
||||
@ -306,6 +409,7 @@ public class QCommandBuilder
|
||||
case DATE -> LocalDate.class;
|
||||
// case TIME -> LocalTime.class;
|
||||
case DATE_TIME -> LocalDateTime.class;
|
||||
case BLOB -> byte[].class;
|
||||
};
|
||||
// @formatter:on
|
||||
}
|
||||
|
@ -23,7 +23,9 @@ package com.kingsrook.qqq.frontend.picocli;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Serializable;
|
||||
@ -32,9 +34,11 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.TableMetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ReportAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
@ -44,15 +48,21 @@ 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.adapters.QInstanceAdapter;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFieldMapping;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.QKeyBasedFieldMapping;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
@ -72,10 +82,17 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import io.github.cdimascio.dotenv.Dotenv;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.logging.log4j.core.config.Configurator;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
import org.jline.utils.Log;
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
import picocli.CommandLine.Model.OptionSpec;
|
||||
@ -94,10 +111,10 @@ import picocli.CommandLine.UnmatchedArgumentException;
|
||||
*******************************************************************************/
|
||||
public class QPicoCliImplementation
|
||||
{
|
||||
public static final int DEFAULT_LIMIT = 20;
|
||||
public static final int DEFAULT_QUERY_LIMIT = 20;
|
||||
|
||||
private static QInstance qInstance;
|
||||
private static QSession session;
|
||||
private static QSession session;
|
||||
|
||||
|
||||
|
||||
@ -112,14 +129,14 @@ public class QPicoCliImplementation
|
||||
// parse args to look up metaData and prime instance
|
||||
if(args.length > 0 && args[0].startsWith("--qInstanceJsonFile="))
|
||||
{
|
||||
String filePath = args[0].replaceFirst("--.*=", "");
|
||||
String filePath = args[0].replaceFirst("--.*=", "");
|
||||
String qInstanceJson = FileUtils.readFileToString(new File(filePath));
|
||||
qInstance = new QInstanceAdapter().jsonToQInstanceIncludingBackends(qInstanceJson);
|
||||
|
||||
String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
|
||||
QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance);
|
||||
int exitCode = qPicoCliImplementation.runCli("qapi", subArgs);
|
||||
int exitCode = qPicoCliImplementation.runCli("qapi", subArgs);
|
||||
System.exit(exitCode);
|
||||
}
|
||||
else
|
||||
@ -136,6 +153,14 @@ public class QPicoCliImplementation
|
||||
*******************************************************************************/
|
||||
public QPicoCliImplementation(QInstance qInstance)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// use the qqq-picocli log4j config, less the system property log4j.configurationFile was set by the runner //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(System.getProperty("log4j.configurationFile") == null)
|
||||
{
|
||||
Configurator.initialize(null, "qqq-picocli-log4j2.xml");
|
||||
}
|
||||
|
||||
QPicoCliImplementation.qInstance = qInstance;
|
||||
}
|
||||
|
||||
@ -229,15 +254,65 @@ public class QPicoCliImplementation
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void setupSession(String[] args) throws QModuleDispatchException
|
||||
private static Optional<Dotenv> loadDotEnv()
|
||||
{
|
||||
Optional<Dotenv> dotenvOptional = Optional.empty();
|
||||
try
|
||||
{
|
||||
dotenvOptional = Optional.of(Dotenv.configure().load());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Log.info("No session information found in environment");
|
||||
}
|
||||
|
||||
return (dotenvOptional);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void setupSession(String[] args) throws QModuleDispatchException, QAuthenticationException
|
||||
{
|
||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication());
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication());
|
||||
|
||||
try
|
||||
{
|
||||
////////////////////////////////////
|
||||
// look for .env environment file //
|
||||
////////////////////////////////////
|
||||
String sessionId = null;
|
||||
Optional<Dotenv> dotenv = loadDotEnv();
|
||||
if(dotenv.isPresent())
|
||||
{
|
||||
sessionId = dotenv.get().get("SESSION_ID");
|
||||
}
|
||||
|
||||
Map<String, String> authenticationContext = new HashMap<>();
|
||||
if(sessionId == null && authenticationModule instanceof Auth0AuthenticationModule)
|
||||
{
|
||||
LineReader lr = LineReaderBuilder.builder().build();
|
||||
String tokenId = lr.readLine("Create a .env file with the contents of the Auth0 JWT Id Token in the variable 'SESSION_ID': \nPress enter once complete...");
|
||||
dotenv = loadDotEnv();
|
||||
if(dotenv.isPresent())
|
||||
{
|
||||
sessionId = dotenv.get().get("SESSION_ID");
|
||||
}
|
||||
}
|
||||
|
||||
authenticationContext.put("sessionId", sessionId);
|
||||
|
||||
// todo - does this need some per-provider logic actually? mmm...
|
||||
session = authenticationModule.createSession(qInstance, authenticationContext);
|
||||
}
|
||||
catch(QAuthenticationException qae)
|
||||
{
|
||||
throw (qae);
|
||||
}
|
||||
|
||||
// todo - does this need some per-provider logic actually? mmm...
|
||||
Map<String, String> authenticationContext = new HashMap<>();
|
||||
authenticationContext.put("sessionId", System.getenv("sessionId"));
|
||||
session = authenticationModule.createSession(authenticationContext);
|
||||
}
|
||||
|
||||
|
||||
@ -254,7 +329,7 @@ public class QPicoCliImplementation
|
||||
else
|
||||
{
|
||||
ParseResult subParseResult = parseResult.subcommand();
|
||||
String subCommandName = subParseResult.commandSpec().name();
|
||||
String subCommandName = subParseResult.commandSpec().name();
|
||||
CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName);
|
||||
switch(subCommandName)
|
||||
{
|
||||
@ -285,7 +360,7 @@ public class QPicoCliImplementation
|
||||
if(tableParseResult.hasSubcommand())
|
||||
{
|
||||
ParseResult subParseResult = tableParseResult.subcommand();
|
||||
String subCommandName = subParseResult.commandSpec().name();
|
||||
String subCommandName = subParseResult.commandSpec().name();
|
||||
switch(subCommandName)
|
||||
{
|
||||
case "meta-data":
|
||||
@ -296,10 +371,19 @@ public class QPicoCliImplementation
|
||||
{
|
||||
return runTableCount(commandLine, tableName, subParseResult);
|
||||
}
|
||||
case "get":
|
||||
{
|
||||
CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName);
|
||||
return runTableGet(commandLine, tableName, subParseResult, subCommandLine);
|
||||
}
|
||||
case "query":
|
||||
{
|
||||
return runTableQuery(commandLine, tableName, subParseResult);
|
||||
}
|
||||
case "export":
|
||||
{
|
||||
return runTableExport(commandLine, tableName, subParseResult);
|
||||
}
|
||||
case "insert":
|
||||
{
|
||||
return runTableInsert(commandLine, tableName, subParseResult);
|
||||
@ -352,7 +436,7 @@ public class QPicoCliImplementation
|
||||
///////////////////////////////////////////
|
||||
// move on to running the actual process //
|
||||
///////////////////////////////////////////
|
||||
String subCommandName = subParseResult.subcommand().commandSpec().name();
|
||||
String subCommandName = subParseResult.subcommand().commandSpec().name();
|
||||
CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName);
|
||||
return runActualProcess(subCommandLine, subParseResult.subcommand());
|
||||
}
|
||||
@ -365,9 +449,9 @@ public class QPicoCliImplementation
|
||||
*******************************************************************************/
|
||||
private int runActualProcess(CommandLine subCommandLine, ParseResult processParseResult)
|
||||
{
|
||||
String processName = processParseResult.commandSpec().name();
|
||||
QProcessMetaData process = qInstance.getProcess(processName);
|
||||
RunProcessInput request = new RunProcessInput(qInstance);
|
||||
String processName = processParseResult.commandSpec().name();
|
||||
QProcessMetaData process = qInstance.getProcess(processName);
|
||||
RunProcessInput request = new RunProcessInput(qInstance);
|
||||
|
||||
request.setSession(session);
|
||||
request.setProcessName(processName);
|
||||
@ -443,6 +527,48 @@ public class QPicoCliImplementation
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private int runTableGet(CommandLine commandLine, String tableName, ParseResult subParseResult, CommandLine subCommandLine) throws QException
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
queryInput.setSession(session);
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setSkip(subParseResult.matchedOptionValue("skip", null));
|
||||
String primaryKeyValue = subParseResult.matchedPositionalValue(0, null);
|
||||
|
||||
if(primaryKeyValue == null)
|
||||
{
|
||||
subCommandLine.usage(commandLine.getOut());
|
||||
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||
}
|
||||
|
||||
QTableMetaData table = queryInput.getTable();
|
||||
QQueryFilter filter = new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria()
|
||||
.withFieldName(table.getPrimaryKeyField())
|
||||
.withOperator(QCriteriaOperator.EQUALS)
|
||||
.withValues(List.of(primaryKeyValue)));
|
||||
queryInput.setFilter(filter);
|
||||
|
||||
QueryAction queryAction = new QueryAction();
|
||||
QueryOutput queryOutput = queryAction.execute(queryInput);
|
||||
List<QRecord> records = queryOutput.getRecords();
|
||||
if(records.isEmpty())
|
||||
{
|
||||
commandLine.getOut().println("No " + table.getLabel() + " found for " + table.getField(table.getPrimaryKeyField()).getLabel() + ": " + primaryKeyValue);
|
||||
return commandLine.getCommandSpec().exitCodeOnInvalidInput();
|
||||
}
|
||||
else
|
||||
{
|
||||
commandLine.getOut().println(JsonUtils.toPrettyJson(records.get(0)));
|
||||
return commandLine.getCommandSpec().exitCodeOnSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -452,7 +578,7 @@ public class QPicoCliImplementation
|
||||
queryInput.setSession(session);
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setSkip(subParseResult.matchedOptionValue("skip", null));
|
||||
queryInput.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_LIMIT));
|
||||
queryInput.setLimit(subParseResult.matchedOptionValue("limit", null));
|
||||
queryInput.setFilter(generateQueryFilter(subParseResult));
|
||||
|
||||
QueryAction queryAction = new QueryAction();
|
||||
@ -463,6 +589,77 @@ public class QPicoCliImplementation
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private int runTableExport(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException
|
||||
{
|
||||
String filename = subParseResult.matchedOptionValue("--filename", "");
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if a format query param wasn't given, then try to get file extension from file name //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
ReportFormat reportFormat;
|
||||
if(filename.contains("."))
|
||||
{
|
||||
reportFormat = ReportFormat.fromString(filename.substring(filename.lastIndexOf(".") + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QUserFacingException("File name did not contain an extension, so report format could not be inferred."));
|
||||
}
|
||||
|
||||
OutputStream outputStream;
|
||||
try
|
||||
{
|
||||
outputStream = new FileOutputStream(filename);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error opening report file: " + e.getMessage(), e));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
// set up the report action's input object //
|
||||
/////////////////////////////////////////////
|
||||
ReportInput reportInput = new ReportInput(qInstance);
|
||||
reportInput.setSession(session);
|
||||
reportInput.setTableName(tableName);
|
||||
reportInput.setReportFormat(reportFormat);
|
||||
reportInput.setFilename(filename);
|
||||
reportInput.setReportOutputStream(outputStream);
|
||||
reportInput.setLimit(subParseResult.matchedOptionValue("limit", null));
|
||||
|
||||
reportInput.setQueryFilter(generateQueryFilter(subParseResult));
|
||||
|
||||
String fieldNames = subParseResult.matchedOptionValue("--fieldNames", "");
|
||||
if(StringUtils.hasContent(fieldNames))
|
||||
{
|
||||
reportInput.setFieldNames(Arrays.asList(fieldNames.split(",")));
|
||||
}
|
||||
|
||||
ReportOutput reportOutput = new ReportAction().execute(reportInput);
|
||||
|
||||
commandLine.getOut().println("Wrote " + reportOutput.getRecordCount() + " records to file " + filename);
|
||||
return commandLine.getCommandSpec().exitCodeOnSuccess();
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
outputStream.close();
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
throw (new QException("Error closing report file", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -474,7 +671,7 @@ public class QPicoCliImplementation
|
||||
for(String criterion : criteria)
|
||||
{
|
||||
// todo - parse!
|
||||
String[] parts = criterion.split(" ");
|
||||
String[] parts = criterion.split(" ");
|
||||
QFilterCriteria qQueryCriteria = new QFilterCriteria();
|
||||
qQueryCriteria.setFieldName(parts[0]);
|
||||
qQueryCriteria.setOperator(QCriteriaOperator.valueOf(parts[1]));
|
||||
@ -504,6 +701,14 @@ public class QPicoCliImplementation
|
||||
String json = subParseResult.matchedOptionValue("--mapping", "");
|
||||
mapping = new JsonToQFieldMappingAdapter().buildMappingFromJson(json);
|
||||
}
|
||||
else
|
||||
{
|
||||
mapping = new QKeyBasedFieldMapping();
|
||||
for(Map.Entry<String, QFieldMetaData> entry : table.getFields().entrySet())
|
||||
{
|
||||
((QKeyBasedFieldMapping) mapping).addMapping(entry.getKey(), entry.getValue().getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// get the records that the user specified //
|
||||
@ -532,7 +737,7 @@ public class QPicoCliImplementation
|
||||
try
|
||||
{
|
||||
String path = subParseResult.matchedOptionValue("--csvFile", "");
|
||||
String csv = FileUtils.readFileToString(new File(path));
|
||||
String csv = FileUtils.readFileToString(new File(path));
|
||||
recordList = new CsvToQRecordAdapter().buildRecordsFromCsv(csv, table, mapping);
|
||||
}
|
||||
catch(IOException e)
|
||||
@ -585,38 +790,81 @@ public class QPicoCliImplementation
|
||||
updateInput.setTableName(tableName);
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
|
||||
List<QRecord> recordList = new ArrayList<>();
|
||||
List<QRecord> recordsToUpdate = new ArrayList<>();
|
||||
boolean anyFields = false;
|
||||
|
||||
boolean anyFields = false;
|
||||
String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", "");
|
||||
String[] criteria = subParseResult.matchedOptionValue("criteria", new String[] {});
|
||||
|
||||
String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", "");
|
||||
Serializable[] primaryKeyValues = primaryKeyOption.split(",");
|
||||
for(Serializable primaryKeyValue : primaryKeyValues)
|
||||
if(StringUtils.hasContent(primaryKeyOption))
|
||||
{
|
||||
QRecord record = new QRecord();
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the primaryKey option was given, split it up and seed the recordToUpdate list //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
Serializable[] primaryKeyValues = primaryKeyOption.split(",");
|
||||
for(Serializable primaryKeyValue : primaryKeyValues)
|
||||
{
|
||||
recordsToUpdate.add(new QRecord().withValue(table.getPrimaryKeyField(), primaryKeyValue));
|
||||
}
|
||||
}
|
||||
else if(criteria.length > 0)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// else if criteria were given, execute the query for the lsit of records to update //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QRecord qRecord : executeQuery(tableName, subParseResult))
|
||||
{
|
||||
recordsToUpdate.add(new QRecord().withValue(table.getPrimaryKeyField(), qRecord.getValue(table.getPrimaryKeyField())));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
commandLine.getErr().println("Error: Either primaryKey or criteria must be specified.");
|
||||
CommandLine subCommandLine = commandLine.getSubcommands().get("update");
|
||||
subCommandLine.usage(commandLine.getOut());
|
||||
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||
}
|
||||
|
||||
recordList.add(record);
|
||||
record.setValue(table.getPrimaryKeyField(), primaryKeyValue);
|
||||
///////////////////////////////////////////////////
|
||||
// make sure at least one --field- arg was given //
|
||||
///////////////////////////////////////////////////
|
||||
for(OptionSpec matchedOption : subParseResult.matchedOptions())
|
||||
{
|
||||
if(matchedOption.longestName().startsWith("--field-"))
|
||||
{
|
||||
anyFields = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!anyFields)
|
||||
{
|
||||
commandLine.getErr().println("Error: At least one field to update must be specified.");
|
||||
CommandLine subCommandLine = commandLine.getSubcommands().get("update");
|
||||
subCommandLine.usage(commandLine.getOut());
|
||||
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||
}
|
||||
|
||||
if(recordsToUpdate.isEmpty())
|
||||
{
|
||||
commandLine.getErr().println("No rows to update were found.");
|
||||
CommandLine subCommandLine = commandLine.getSubcommands().get("update");
|
||||
subCommandLine.usage(commandLine.getOut());
|
||||
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||
}
|
||||
|
||||
for(QRecord record : recordsToUpdate)
|
||||
{
|
||||
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 || recordList.isEmpty())
|
||||
{
|
||||
CommandLine subCommandLine = commandLine.getSubcommands().get("update");
|
||||
subCommandLine.usage(commandLine.getOut());
|
||||
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||
}
|
||||
|
||||
updateInput.setRecords(recordList);
|
||||
updateInput.setRecords(recordsToUpdate);
|
||||
|
||||
UpdateAction updateAction = new UpdateAction();
|
||||
UpdateOutput updateResult = updateAction.execute(updateInput);
|
||||
@ -638,9 +886,24 @@ public class QPicoCliImplementation
|
||||
/////////////////////////////////////////////
|
||||
// get the pKeys that the user specified //
|
||||
/////////////////////////////////////////////
|
||||
String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", "");
|
||||
Serializable[] primaryKeyValues = primaryKeyOption.split(",");
|
||||
deleteInput.setPrimaryKeys(Arrays.asList(primaryKeyValues));
|
||||
String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", "");
|
||||
String[] criteria = subParseResult.matchedOptionValue("criteria", new String[] {});
|
||||
|
||||
if(StringUtils.hasContent(primaryKeyOption))
|
||||
{
|
||||
deleteInput.setPrimaryKeys(Arrays.asList(primaryKeyOption.split(",")));
|
||||
}
|
||||
else if(criteria.length > 0)
|
||||
{
|
||||
deleteInput.setQueryFilter(generateQueryFilter(subParseResult));
|
||||
}
|
||||
else
|
||||
{
|
||||
commandLine.getErr().println("Error: Either primaryKey or criteria must be specified.");
|
||||
CommandLine subCommandLine = commandLine.getSubcommands().get("delete");
|
||||
subCommandLine.usage(commandLine.getOut());
|
||||
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||
}
|
||||
|
||||
DeleteAction deleteAction = new DeleteAction();
|
||||
DeleteOutput deleteResult = deleteAction.execute(deleteInput);
|
||||
@ -668,4 +931,21 @@ public class QPicoCliImplementation
|
||||
commandLine.usage(commandLine.getOut());
|
||||
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<QRecord> executeQuery(String tableName, ParseResult subParseResult) throws QException
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
queryInput.setSession(session);
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setFilter(generateQueryFilter(subParseResult));
|
||||
|
||||
QueryAction queryAction = new QueryAction();
|
||||
QueryOutput queryOutput = queryAction.execute(queryInput);
|
||||
return (queryOutput.getRecords());
|
||||
}
|
||||
}
|
||||
|
18
src/main/resources/qqq-picocli-log4j2.xml
Normal file
18
src/main/resources/qqq-picocli-log4j2.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration>
|
||||
<Properties>
|
||||
<Property name="LOG_PATTERN">%date{ISO8601} | %relative | %level | %threadName{1} | %logger{1}.%method | %message%n</Property>
|
||||
</Properties>
|
||||
<Appenders>
|
||||
<File name="LogFile" fileName="log/qqq-picocli.log">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
</File>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="org.apache.log4j.xml" additivity="false">
|
||||
</Logger>
|
||||
<Root level="all">
|
||||
<AppenderRef ref="LogFile"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
@ -31,6 +31,7 @@ import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
@ -40,6 +41,7 @@ import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@ -226,7 +228,7 @@ class QPicoCliImplementationTest
|
||||
int count = countResult.getInt("count");
|
||||
assertEquals(4, count);
|
||||
|
||||
testOutput = testCli("person", "count", "--criteria", "id EQUALS 3");
|
||||
testOutput = testCli("person", "count", "--criteria", "id EQUALS 3");
|
||||
countResult = JsonUtils.toJSONObject(testOutput.getOutput());
|
||||
assertNotNull(countResult);
|
||||
count = countResult.getInt("count");
|
||||
@ -254,6 +256,49 @@ class QPicoCliImplementationTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a "get single record" action (singleton query) on a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableGetNoIdGiven()
|
||||
{
|
||||
TestOutput testOutput = testCli("person", "get");
|
||||
assertTestOutputContains(testOutput, "Usage: " + CLI_NAME + " person get PARAM");
|
||||
assertTestOutputContains(testOutput, "Primary key value from the table");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a "get single record" action (singleton query) on a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableGet()
|
||||
{
|
||||
TestOutput testOutput = testCli("person", "get", "1");
|
||||
JSONObject getResult = JsonUtils.toJSONObject(testOutput.getOutput());
|
||||
assertNotNull(getResult);
|
||||
assertEquals(1, getResult.getJSONObject("values").getInt("id"));
|
||||
assertEquals("Darin", getResult.getJSONObject("values").getString("firstName"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a "get single record" action (singleton query) on a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableGetMissingId()
|
||||
{
|
||||
TestOutput testOutput = testCli("person", "get", "1976");
|
||||
assertTestOutputContains(testOutput, "No Person found for Id: 1976");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running an insert w/o specifying any fields, prints usage
|
||||
**
|
||||
@ -402,11 +447,24 @@ class QPicoCliImplementationTest
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running an update w/ fields as arguments
|
||||
** test running an update w/o specifying any pkeys or criteria, prints usage
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableUpdateFieldArguments() throws Exception
|
||||
public void test_tableUpdateNoRecordsPrintsUsage()
|
||||
{
|
||||
TestOutput testOutput = testCli("person", "update", "--field-firstName=Lucy");
|
||||
assertTestOutputContains(testOutput, "Usage: " + CLI_NAME + " person update");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running an update w/ fields as arguments and one primary key
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableUpdateFieldArgumentsOnePrimaryKey() throws Exception
|
||||
{
|
||||
assertRowValueById("person", "first_name", "Garret", 5);
|
||||
TestOutput testOutput = testCli("person", "update",
|
||||
@ -422,6 +480,59 @@ class QPicoCliImplementationTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running an update w/ fields as arguments and multiple primary keys
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableUpdateFieldArgumentsManyPrimaryKeys() throws Exception
|
||||
{
|
||||
assertRowValueById("person", "first_name", "Tyler", 4);
|
||||
assertRowValueById("person", "first_name", "Garret", 5);
|
||||
TestOutput testOutput = testCli("person", "update",
|
||||
"--primaryKey=4,5",
|
||||
"--field-birthDate=1980-05-31",
|
||||
"--field-firstName=Lucy",
|
||||
"--field-lastName=Lu");
|
||||
JSONObject updateResult = JsonUtils.toJSONObject(testOutput.getOutput());
|
||||
assertNotNull(updateResult);
|
||||
assertEquals(2, updateResult.getJSONArray("records").length());
|
||||
assertEquals(4, updateResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getInt("id"));
|
||||
assertEquals(5, updateResult.getJSONArray("records").getJSONObject(1).getJSONObject("values").getInt("id"));
|
||||
assertRowValueById("person", "first_name", "Lucy", 4);
|
||||
assertRowValueById("person", "first_name", "Lucy", 5);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running an update w/ fields as arguments and a criteria
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableUpdateFieldArgumentsCriteria() throws Exception
|
||||
{
|
||||
assertRowValueById("person", "first_name", "Tyler", 4);
|
||||
assertRowValueById("person", "first_name", "Garret", 5);
|
||||
TestOutput testOutput = testCli("person", "update",
|
||||
"--criteria",
|
||||
"id GREATER_THAN_OR_EQUALS 4",
|
||||
"--field-firstName=Lucy",
|
||||
"--field-lastName=Lu");
|
||||
JSONObject updateResult = JsonUtils.toJSONObject(testOutput.getOutput());
|
||||
assertNotNull(updateResult);
|
||||
assertEquals(2, updateResult.getJSONArray("records").length());
|
||||
assertEquals(4, updateResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getInt("id"));
|
||||
assertEquals(5, updateResult.getJSONArray("records").getJSONObject(1).getJSONObject("values").getInt("id"));
|
||||
assertRowValueById("person", "first_name", "Lucy", 4);
|
||||
assertRowValueById("person", "first_name", "Lucy", 5);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void assertRowValueById(String tableName, String columnName, String value, Integer id) throws Exception
|
||||
{
|
||||
TestUtils.runTestSql("SELECT " + columnName + " FROM " + tableName + " WHERE id=" + id, (rs -> {
|
||||
@ -438,6 +549,18 @@ class QPicoCliImplementationTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a delete without enough args
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableDeleteWithoutArgs() throws Exception
|
||||
{
|
||||
TestOutput testOutput = testCli("person", "delete");
|
||||
assertTestOutputContains(testOutput, "Usage: " + CLI_NAME + " person delete");
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a delete against a table
|
||||
**
|
||||
@ -448,10 +571,7 @@ class QPicoCliImplementationTest
|
||||
TestOutput testOutput = testCli("person", "delete", "--primaryKey", "2,4");
|
||||
JSONObject deleteResult = JsonUtils.toJSONObject(testOutput.getOutput());
|
||||
assertNotNull(deleteResult);
|
||||
JSONArray records = deleteResult.getJSONArray("records");
|
||||
assertEquals(2, records.length());
|
||||
assertEquals(2, records.getJSONObject(0).getJSONObject("values").getInt("id"));
|
||||
assertEquals(4, records.getJSONObject(1).getJSONObject("values").getInt("id"));
|
||||
assertEquals(2, deleteResult.getInt("deletedRecordCount"));
|
||||
TestUtils.runTestSql("SELECT id FROM person", (rs -> {
|
||||
int rowsFound = 0;
|
||||
while(rs.next())
|
||||
@ -470,7 +590,7 @@ class QPicoCliImplementationTest
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableProcess() throws Exception
|
||||
public void test_tableProcess()
|
||||
{
|
||||
TestOutput testOutput = testCli("person", "process");
|
||||
|
||||
@ -487,7 +607,7 @@ class QPicoCliImplementationTest
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableProcessUnknownName() throws Exception
|
||||
public void test_tableProcessUnknownName()
|
||||
{
|
||||
String badProcessName = "not-a-process";
|
||||
TestOutput testOutput = testCli("person", "process", badProcessName);
|
||||
@ -502,7 +622,7 @@ class QPicoCliImplementationTest
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableProcessGreetUsingCallbackForFields() throws Exception
|
||||
public void test_tableProcessGreetUsingCallbackForFields()
|
||||
{
|
||||
setStandardInputLines("Hi", "How are you?");
|
||||
TestOutput testOutput = testCli("person", "process", "greet");
|
||||
@ -511,12 +631,217 @@ class QPicoCliImplementationTest
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test exporting a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableExportNoArgsExcel()
|
||||
{
|
||||
String filename = "/tmp/" + UUID.randomUUID() + ".xlsx";
|
||||
TestOutput testOutput = testCli("person", "export", "--filename=" + filename);
|
||||
assertTestOutputContains(testOutput, "Wrote 5 records to file " + filename);
|
||||
|
||||
File file = new File(filename);
|
||||
assertTrue(file.exists());
|
||||
|
||||
// todo - some day when we learn to read Excel, assert that we wrote as expected.
|
||||
|
||||
deleteFile(file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test exporting a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableExportWithLimit() throws Exception
|
||||
{
|
||||
String filename = "/tmp/" + UUID.randomUUID() + ".csv";
|
||||
TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--limit=3");
|
||||
assertTestOutputContains(testOutput, "Wrote 3 records to file " + filename);
|
||||
|
||||
File file = new File(filename);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> list = FileUtils.readLines(file);
|
||||
assertEquals(4, list.size());
|
||||
assertThat(list.get(0)).contains("""
|
||||
"Id","Create Date","Modify Date\"""");
|
||||
assertThat(list.get(1)).matches("""
|
||||
^"1",.*"Darin.*""");
|
||||
assertThat(list.get(3)).matches("""
|
||||
^"3",.*"Tim.*""");
|
||||
|
||||
deleteFile(file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test exporting a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableExportWithCriteria() throws Exception
|
||||
{
|
||||
String filename = "/tmp/" + UUID.randomUUID() + ".csv";
|
||||
TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--criteria", "id NOT_EQUALS 3");
|
||||
assertTestOutputContains(testOutput, "Wrote 4 records to file " + filename);
|
||||
|
||||
File file = new File(filename);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> list = FileUtils.readLines(file);
|
||||
assertEquals(5, list.size());
|
||||
assertThat(list.get(0)).contains("""
|
||||
"Id","Create Date","Modify Date\"""");
|
||||
assertThat(list.get(1)).matches("^\"1\",.*");
|
||||
assertThat(list.get(2)).matches("^\"2\",.*");
|
||||
assertThat(list.get(3)).matches("^\"4\",.*");
|
||||
assertThat(list.get(4)).matches("^\"5\",.*");
|
||||
|
||||
deleteFile(file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test exporting a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableExportWithoutFilename()
|
||||
{
|
||||
TestOutput testOutput = testCli("person", "export");
|
||||
assertTestErrorContains(testOutput, "Missing required option: '--filename=PARAM'");
|
||||
assertTestErrorContains(testOutput, "Usage: " + CLI_NAME + " person export");
|
||||
assertTestErrorContains(testOutput, "-f=PARAM");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test exporting a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableExportNoFileExtension()
|
||||
{
|
||||
String filename = "/tmp/" + UUID.randomUUID();
|
||||
TestOutput testOutput = testCli("person", "export", "--filename=" + filename);
|
||||
assertTestErrorContains(testOutput, "File name did not contain an extension");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test exporting a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableExportBadFileType()
|
||||
{
|
||||
String filename = "/tmp/" + UUID.randomUUID() + ".docx";
|
||||
TestOutput testOutput = testCli("person", "export", "--filename=" + filename);
|
||||
assertTestErrorContains(testOutput, "Unsupported report format: docx.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test exporting a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableExportBadFilePath()
|
||||
{
|
||||
String filename = "/no-such/directory/" + UUID.randomUUID() + "report.csv";
|
||||
TestOutput testOutput = testCli("person", "export", "--filename=" + filename);
|
||||
assertTestErrorContains(testOutput, "No such file or directory");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test exporting a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableExportBadFieldNams()
|
||||
{
|
||||
String filename = "/tmp/" + UUID.randomUUID() + ".csv";
|
||||
TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--fieldNames=foo");
|
||||
assertTestErrorContains(testOutput, "Field name foo was not found on the Person table");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test exporting a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableExportBadFieldNames()
|
||||
{
|
||||
String filename = "/tmp/" + UUID.randomUUID() + ".csv";
|
||||
TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--fieldNames=foo,bar,baz");
|
||||
assertTestErrorContains(testOutput, "Fields names foo, bar, and baz were not found on the Person table");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test exporting a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableExportGoodFieldNamesXslx() throws IOException
|
||||
{
|
||||
String filename = "/tmp/" + UUID.randomUUID() + ".xlsx";
|
||||
TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--fieldNames=id,lastName,birthDate");
|
||||
|
||||
File file = new File(filename);
|
||||
assertTrue(file.exists());
|
||||
|
||||
// todo - some day when we learn to read Excel, assert that we wrote as expected (with 3 columns)
|
||||
|
||||
deleteFile(file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test exporting a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableExportGoodFieldNamesCSV() throws IOException
|
||||
{
|
||||
String filename = "/tmp/" + UUID.randomUUID() + ".csv";
|
||||
TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--fieldNames=id,lastName,birthDate");
|
||||
|
||||
File file = new File(filename);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> list = FileUtils.readLines(file);
|
||||
assertEquals(6, list.size());
|
||||
assertThat(list.get(0)).isEqualTo("""
|
||||
"Id","Last Name","Birth Date\"""");
|
||||
assertThat(list.get(1)).isEqualTo("""
|
||||
"1","Kelkhoff","1980-05-31\"""");
|
||||
|
||||
deleteFile(file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a process on a table
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_tableProcessGreetUsingOptionsForFields() throws Exception
|
||||
public void test_tableProcessGreetUsingOptionsForFields()
|
||||
{
|
||||
TestOutput testOutput = testCli("person", "process", "greet", "--field-greetingPrefix=Hello", "--field-greetingSuffix=World");
|
||||
assertTestOutputDoesNotContain(testOutput, "Please supply a value for the field");
|
||||
@ -567,6 +892,16 @@ class QPicoCliImplementationTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** delete a file, asserting that we did so.
|
||||
*******************************************************************************/
|
||||
private void deleteFile(File file)
|
||||
{
|
||||
assertTrue(file.delete());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -628,118 +963,4 @@ class QPicoCliImplementationTest
|
||||
System.setIn(stdin);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
136
src/test/java/com/kingsrook/qqq/frontend/picocli/TestOutput.java
Normal file
136
src/test/java/com/kingsrook/qqq/frontend/picocli/TestOutput.java
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.picocli;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
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;
|
||||
}
|
||||
}
|
@ -25,8 +25,9 @@ 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.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
@ -112,7 +113,7 @@ public class TestUtils
|
||||
{
|
||||
return new QAuthenticationMetaData()
|
||||
.withName("mock")
|
||||
.withType("mock");
|
||||
.withType(QAuthenticationType.MOCK);
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user