Adding comments and more tests

This commit is contained in:
Darin Kelkhoff
2021-12-01 22:57:45 -06:00
parent c0ad05d119
commit 90cd12e09f
3 changed files with 181 additions and 22 deletions

View File

@ -5,7 +5,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -41,8 +40,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.JsonUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import picocli.CommandLine; import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec; import picocli.CommandLine.Model.OptionSpec;
@ -52,14 +49,15 @@ import picocli.CommandLine.UnmatchedArgumentException;
/******************************************************************************* /*******************************************************************************
** QQQ PicoCLI implementation. Given a QInstance, produces an entire CLI
** for working with all tables in that instance.
**
** Note: Please do not use System.out or .err here -- rather, use the CommandLine ** 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! ** object's out & err members - so the unit test can see the output!
* **
*******************************************************************************/ *******************************************************************************/
public class QPicoCliImplementation public class QPicoCliImplementation
{ {
private static final Logger LOG = LogManager.getLogger(QPicoCliImplementation.class);
public static final int DEFAULT_LIMIT = 20; public static final int DEFAULT_LIMIT = 20;
private static QInstance qInstance; private static QInstance qInstance;
@ -274,7 +272,6 @@ public class QPicoCliImplementation
{ {
CommandSpec deleteCommand = CommandSpec.create(); CommandSpec deleteCommand = CommandSpec.create();
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
deleteCommand.addOption(OptionSpec.builder("--primaryKey") deleteCommand.addOption(OptionSpec.builder("--primaryKey")
.type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type? .type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type?
.build()); .build());
@ -287,9 +284,9 @@ public class QPicoCliImplementation
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@SuppressWarnings("checkstyle:Indentation")
private Class<?> getClassForField(QFieldMetaData field) private Class<?> getClassForField(QFieldMetaData field)
{ {
// @formatter:off // IJ can't do new-style switch correctly yet...
return switch(field.getType()) return switch(field.getType())
{ {
case STRING, TEXT, HTML, PASSWORD -> String.class; case STRING, TEXT, HTML, PASSWORD -> String.class;
@ -298,8 +295,8 @@ public class QPicoCliImplementation
case DATE -> LocalDate.class; case DATE -> LocalDate.class;
// case TIME -> LocalTime.class; // case TIME -> LocalTime.class;
case DATE_TIME -> LocalDateTime.class; case DATE_TIME -> LocalDateTime.class;
default -> throw new IllegalStateException("Unsupported field type: " + field.getType());
}; };
// @formatter:on
} }
@ -436,7 +433,7 @@ public class QPicoCliImplementation
///////////////////////////////////////////// /////////////////////////////////////////////
// get the records that the user specified // // get the records that the user specified //
///////////////////////////////////////////// /////////////////////////////////////////////
List<QRecord> recordList = null; List<QRecord> recordList;
if(subParseResult.hasMatchedOption("--jsonBody")) if(subParseResult.hasMatchedOption("--jsonBody"))
{ {
String json = subParseResult.matchedOptionValue("--jsonBody", ""); String json = subParseResult.matchedOptionValue("--jsonBody", "");
@ -502,6 +499,7 @@ public class QPicoCliImplementation
} }
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -509,13 +507,10 @@ public class QPicoCliImplementation
{ {
DeleteRequest deleteRequest = new DeleteRequest(qInstance); DeleteRequest deleteRequest = new DeleteRequest(qInstance);
deleteRequest.setTableName(tableName); deleteRequest.setTableName(tableName);
QTableMetaData table = qInstance.getTable(tableName);
///////////////////////////////////////////// /////////////////////////////////////////////
// get the pKeys that the user specified // // get the pKeys that the user specified //
///////////////////////////////////////////// /////////////////////////////////////////////
List<Serializable> primaryKeyList = null;
String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", "");
String[] primaryKeyValues = primaryKeyOption.split(","); String[] primaryKeyValues = primaryKeyOption.split(",");
deleteRequest.setPrimaryKeys(Arrays.asList(primaryKeyValues)); deleteRequest.setPrimaryKeys(Arrays.asList(primaryKeyValues));

View File

@ -2,12 +2,17 @@ package com.kingsrook.qqq.frontend.picocli;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.JsonUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.apache.commons.io.FileUtils;
import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -17,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/******************************************************************************* /*******************************************************************************
** Unit test for the QPicoCliImplementation.
** **
*******************************************************************************/ *******************************************************************************/
class QPicoCliImplementationTest class QPicoCliImplementationTest
@ -27,6 +33,7 @@ class QPicoCliImplementationTest
/******************************************************************************* /*******************************************************************************
** Fully rebuild the test-database before each test runs, for completely known state.
** **
*******************************************************************************/ *******************************************************************************/
@BeforeEach @BeforeEach
@ -38,6 +45,7 @@ class QPicoCliImplementationTest
/******************************************************************************* /*******************************************************************************
** test that w/ no arguments you just get usage.
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
@ -50,6 +58,7 @@ class QPicoCliImplementationTest
/******************************************************************************* /*******************************************************************************
** test that --help gives you usage.
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
@ -63,6 +72,7 @@ class QPicoCliImplementationTest
/******************************************************************************* /*******************************************************************************
** test the --verion argument
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
@ -75,6 +85,7 @@ class QPicoCliImplementationTest
/******************************************************************************* /*******************************************************************************
** Test that an unrecognized opttion gives an error
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
@ -89,6 +100,7 @@ class QPicoCliImplementationTest
/******************************************************************************* /*******************************************************************************
** test the top-level --meta-data option
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
@ -108,6 +120,7 @@ class QPicoCliImplementationTest
/******************************************************************************* /*******************************************************************************
** test giving a table-name, gives usage for that table
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
@ -121,6 +134,22 @@ class QPicoCliImplementationTest
/******************************************************************************* /*******************************************************************************
** test unknown command under table, prints error and usage.
**
*******************************************************************************/
@Test
public void test_tableUnknownCommand()
{
String badCommand = "qwuijibo";
TestOutput testOutput = testCli("person", badCommand);
assertTrue(testOutput.getError().contains("Unmatched argument at index 1: '" + badCommand + "'"));
assertTrue(testOutput.getError().contains("Usage: " + CLI_NAME + " person [COMMAND]"));
}
/*******************************************************************************
** test requesting table meta-data
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
@ -144,6 +173,7 @@ class QPicoCliImplementationTest
/******************************************************************************* /*******************************************************************************
** test running a query on a table
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
@ -161,6 +191,134 @@ class QPicoCliImplementationTest
/******************************************************************************* /*******************************************************************************
** test running an insert w/o specifying any fields, prints usage
**
*******************************************************************************/
@Test
public void test_tableInsertNoFieldsPrintsUsage()
{
TestOutput testOutput = testCli("person", "insert");
assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME + " person insert"));
}
/*******************************************************************************
** test running an insert w/ fields as arguments
**
*******************************************************************************/
@Test
public void test_tableInsertFieldArguments()
{
TestOutput testOutput = testCli("person", "insert",
"--field-firstName=Lucy",
"--field-lastName=Lu");
JSONObject insertResult = JsonUtils.toJSONObject(testOutput.getOutput());
assertNotNull(insertResult);
assertEquals(1, insertResult.getJSONArray("records").length());
assertEquals(6, insertResult.getJSONArray("records").getJSONObject(0).getInt("primaryKey"));
}
/*******************************************************************************
** test running an insert w/ a mapping and json as an argument
**
*******************************************************************************/
@Test
public void test_tableInsertJsonObjectArgumentWithMapping()
{
String mapping = """
--mapping={"firstName":"first","lastName":"ln"}
""";
String jsonBody = """
--jsonBody={"first":"Chester","ln":"Cheese"}
""";
TestOutput testOutput = testCli("person", "insert", mapping, jsonBody);
JSONObject insertResult = JsonUtils.toJSONObject(testOutput.getOutput());
assertNotNull(insertResult);
assertEquals(1, insertResult.getJSONArray("records").length());
assertEquals(6, insertResult.getJSONArray("records").getJSONObject(0).getInt("primaryKey"));
assertEquals("Chester", insertResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getString("firstName"));
assertEquals("Cheese", insertResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getString("lastName"));
}
/*******************************************************************************
** test running an insert w/ a mapping and json as a multi-record file
**
*******************************************************************************/
@Test
public void test_tableInsertJsonArrayFileWithMapping() throws IOException
{
String mapping = """
--mapping={"firstName":"first","lastName":"ln"}
""";
String jsonContents = """
[{"first":"Charlie","ln":"Bear"},{"first":"Coco","ln":"Bean"}]
""";
File file = new File("/tmp/" + UUID.randomUUID() + ".json");
file.deleteOnExit();
FileUtils.writeStringToFile(file, jsonContents);
TestOutput testOutput = testCli("person", "insert", mapping, "--jsonFile=" + file.getAbsolutePath());
JSONObject insertResult = JsonUtils.toJSONObject(testOutput.getOutput());
assertNotNull(insertResult);
JSONArray records = insertResult.getJSONArray("records");
assertEquals(2, records.length());
assertEquals(6, records.getJSONObject(0).getInt("primaryKey"));
assertEquals(7, records.getJSONObject(1).getInt("primaryKey"));
assertEquals("Charlie", records.getJSONObject(0).getJSONObject("values").getString("firstName"));
assertEquals("Bear", records.getJSONObject(0).getJSONObject("values").getString("lastName"));
assertEquals("Coco", records.getJSONObject(1).getJSONObject("values").getString("firstName"));
assertEquals("Bean", records.getJSONObject(1).getJSONObject("values").getString("lastName"));
}
/*******************************************************************************
** test running an insert w/ an index-based mapping and csv file
**
*******************************************************************************/
@Test
public void test_tableInsertCsvFileWithIndexMapping() throws IOException
{
String mapping = """
--mapping={"firstName":1,"lastName":3}
""";
String csvContents = """
"Louis","P","Willikers",1024,
"Nestle","G","Crunch",1701,
""";
File file = new File("/tmp/" + UUID.randomUUID() + ".csv");
file.deleteOnExit();
FileUtils.writeStringToFile(file, csvContents);
TestOutput testOutput = testCli("person", "insert", mapping, "--csvFile=" + file.getAbsolutePath());
JSONObject insertResult = JsonUtils.toJSONObject(testOutput.getOutput());
assertNotNull(insertResult);
JSONArray records = insertResult.getJSONArray("records");
assertEquals(2, records.length());
assertEquals(6, records.getJSONObject(0).getInt("primaryKey"));
assertEquals(7, records.getJSONObject(1).getInt("primaryKey"));
assertEquals("Louis", records.getJSONObject(0).getJSONObject("values").getString("firstName"));
assertEquals("Willikers", records.getJSONObject(0).getJSONObject("values").getString("lastName"));
assertEquals("Nestle", records.getJSONObject(1).getJSONObject("values").getString("firstName"));
assertEquals("Crunch", records.getJSONObject(1).getJSONObject("values").getString("lastName"));
}
/*******************************************************************************
** test running a delete against a table
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test

View File

@ -17,12 +17,14 @@ import static junit.framework.Assert.assertNotNull;
/******************************************************************************* /*******************************************************************************
** Utility methods for unit tests.
** **
*******************************************************************************/ *******************************************************************************/
public class TestUtils public class TestUtils
{ {
/******************************************************************************* /*******************************************************************************
** Prime a test database (e.g., h2, in-memory)
** **
*******************************************************************************/ *******************************************************************************/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -43,6 +45,7 @@ public class TestUtils
/******************************************************************************* /*******************************************************************************
** Run an SQL Query in the test database
** **
*******************************************************************************/ *******************************************************************************/
public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception
@ -55,6 +58,7 @@ public class TestUtils
/******************************************************************************* /*******************************************************************************
** Define the q-instance for testing (h2 rdbms and 'person' table)
** **
*******************************************************************************/ *******************************************************************************/
public static QInstance defineInstance() public static QInstance defineInstance()
@ -68,6 +72,7 @@ public class TestUtils
/******************************************************************************* /*******************************************************************************
** Define the h2 rdbms backend
** **
*******************************************************************************/ *******************************************************************************/
public static QBackendMetaData defineBackend() public static QBackendMetaData defineBackend()
@ -85,6 +90,7 @@ public class TestUtils
/******************************************************************************* /*******************************************************************************
** Define the person table
** **
*******************************************************************************/ *******************************************************************************/
public static QTableMetaData defineTablePerson() public static QTableMetaData defineTablePerson()