Merged dev into feature/join-enhancements

This commit is contained in:
2024-04-24 14:59:32 -05:00
169 changed files with 11747 additions and 801 deletions

View File

@ -36,6 +36,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportOutput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -43,6 +44,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.LocalMacDevUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
@ -118,6 +120,26 @@ class ExportActionTest extends BaseTest
runReport(recordCount, filename, ReportFormat.XLSX, true);
File file = new File(filename);
LocalMacDevUtils.openFile(file.getAbsolutePath());
assertTrue(file.delete());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testExcelPOI() throws Exception
{
int recordCount = 1000;
String filename = "/tmp/ReportActionTest-POI.xlsx";
runReport(recordCount, filename, ReportFormat.XLSX, true);
File file = new File(filename);
LocalMacDevUtils.openFile(file.getAbsolutePath());
assertTrue(file.delete());
}
@ -147,9 +169,10 @@ class ExportActionTest extends BaseTest
ExportInput exportInput = new ExportInput();
exportInput.setTableName(TestUtils.TABLE_NAME_ORDER);
exportInput.setReportFormat(ReportFormat.CSV);
ByteArrayOutputStream reportOutputStream = new ByteArrayOutputStream();
exportInput.setReportOutputStream(reportOutputStream);
exportInput.setReportDestination(new ReportDestination()
.withReportFormat(ReportFormat.CSV)
.withReportOutputStream(reportOutputStream));
exportInput.setQueryFilter(new QQueryFilter());
exportInput.setFieldNames(List.of("id", "orderNo", "storeId", "orderLine.id", "orderLine.sku", "orderLine.quantity"));
// exportInput.setFieldNames(List.of("id", "orderNo", "storeId"));
@ -197,8 +220,7 @@ class ExportActionTest extends BaseTest
exportInput.setTableName("person");
QTableMetaData table = exportInput.getTable();
exportInput.setReportFormat(reportFormat);
exportInput.setReportOutputStream(outputStream);
exportInput.setReportDestination(new ReportDestination().withReportFormat(reportFormat).withReportOutputStream(outputStream));
exportInput.setQueryFilter(new QQueryFilter());
exportInput.setLimit(recordCount);
@ -243,7 +265,7 @@ class ExportActionTest extends BaseTest
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// use xlsx, which has a max-rows limit, to verify that code runs, but doesn't throw when there aren't too many rows //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
exportInput.setReportFormat(ReportFormat.XLSX);
exportInput.setReportDestination(new ReportDestination().withReportFormat(ReportFormat.XLSX));
new ExportAction().preExecute(exportInput);
@ -278,7 +300,7 @@ class ExportActionTest extends BaseTest
////////////////////////////////////////////////////////////////
// use xlsx, which has a max-cols limit, to verify that code. //
////////////////////////////////////////////////////////////////
exportInput.setReportFormat(ReportFormat.XLSX);
exportInput.setReportDestination(new ReportDestination().withReportFormat(ReportFormat.XLSX));
assertThrows(QUserFacingException.class, () ->
{

View File

@ -23,19 +23,33 @@ package com.kingsrook.qqq.backend.core.actions.reporting;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.fastexcel.ExcelFastexcelExportStreamer;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.BoldHeaderAndFooterPoiExcelStyler;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.ExcelPoiBasedStreamingExportStreamer;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.PoiExcelStylerInterface;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
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.pivottable.PivotTableDefinition;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableFunction;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
@ -51,7 +65,17 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.ReportType;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
import com.kingsrook.qqq.backend.core.testutils.PersonQRecord;
import com.kingsrook.qqq.backend.core.utils.LocalMacDevUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFPivotTable;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -85,14 +109,14 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void testPivot1() throws QException
void testSummary1() throws QException
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(definePersonShoesPivotReport(true));
qInstance.addReport(definePersonShoesSummaryReport(true));
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1), "endDate", LocalDate.of(1980, Month.DECEMBER, 31)));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("summary");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(3, list.size());
@ -140,10 +164,10 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void testPivot2() throws QException
void testSummary2() throws QException
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = definePersonShoesPivotReport(false);
QReportMetaData report = definePersonShoesSummaryReport(false);
//////////////////////////////////////////////
// change from the default to sort reversed //
@ -153,7 +177,7 @@ public class GenerateReportActionTest extends BaseTest
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1), "endDate", LocalDate.of(1980, Month.DECEMBER, 31)));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("summary");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(2, list.size());
@ -172,10 +196,10 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void testPivot3() throws QException
void testSummary3() throws QException
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = definePersonShoesPivotReport(false);
QReportMetaData report = definePersonShoesSummaryReport(false);
//////////////////////////////////////////////////////////////////////////////////////////////
// remove the filters, change to sort by personCount (to get some ties), then sumPrice desc //
@ -187,7 +211,7 @@ public class GenerateReportActionTest extends BaseTest
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.now(), "endDate", LocalDate.now()));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("summary");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
@ -224,16 +248,16 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void testPivot4() throws QException
void testSummary4() throws QException
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = definePersonShoesPivotReport(false);
QReportMetaData report = definePersonShoesSummaryReport(false);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// remove the filter, change to have 2 pivot columns - homeStateId and lastName - we should get no roll-up like this. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
report.getDataSources().get(0).getQueryFilter().setCriteria(null);
report.getViews().get(0).setPivotFields(List.of(
report.getViews().get(0).setSummaryFields(List.of(
"homeStateId",
"lastName"
));
@ -241,7 +265,7 @@ public class GenerateReportActionTest extends BaseTest
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.now(), "endDate", LocalDate.now()));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("summary");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(6, list.size());
@ -282,21 +306,21 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void testPivot5() throws QException
void testSummary5() throws QException
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = definePersonShoesPivotReport(false);
QReportMetaData report = definePersonShoesSummaryReport(false);
/////////////////////////////////////////////////////////////////////////////////////
// remove the filter, and just pivot on homeStateId - should aggregate differently //
/////////////////////////////////////////////////////////////////////////////////////
report.getDataSources().get(0).getQueryFilter().setCriteria(null);
report.getViews().get(0).setPivotFields(List.of("homeStateId"));
report.getViews().get(0).setSummaryFields(List.of("homeStateId"));
qInstance.addReport(report);
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.now(), "endDate", LocalDate.now()));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("summary");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(2, list.size());
@ -315,23 +339,18 @@ public class GenerateReportActionTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void runToCsv() throws Exception
private String runToString(ReportFormat reportFormat, String reportName) throws Exception
{
String name = "/tmp/report.csv";
String name = "/tmp/report." + reportFormat.getExtension();
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(definePersonShoesPivotReport(true));
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportFormat(ReportFormat.CSV);
reportInput.setReportOutputStream(fileOutputStream);
reportInput.setReportName(reportName);
reportInput.setReportDestination(new ReportDestination().withReportFormat(reportFormat).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
return (FileUtils.readFileToString(new File(name), StandardCharsets.UTF_8));
}
}
@ -341,27 +360,189 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void runToXlsx() throws Exception
void runSummaryToXlsx() throws Exception
{
String name = "/tmp/report.xlsx";
ReportFormat format = ReportFormat.XLSX;
String name = "/tmp/report-" + format + ".xlsx";
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(definePersonShoesPivotReport(true));
qInstance.addReport(definePersonShoesSummaryReport(true));
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportFormat(ReportFormat.XLSX);
reportInput.setReportOutputStream(fileOutputStream);
reportInput.setReportDestination(new ReportDestination().withReportFormat(format).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
LocalMacDevUtils.openFile(name);
}
}
/*******************************************************************************
** Keep some test coverage on the fastexcel library (as long as we keep it around)
*******************************************************************************/
@Test
void runTableToXlsxFastexcel() throws Exception
{
ReportFormat format = ReportFormat.XLSX;
String name = "/tmp/report-fastexcel.xlsx";
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(defineTableOnlyReport());
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportDestination(new ReportDestination().withReportFormat(format).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
reportInput.setOverrideExportStreamerSupplier(ExcelFastexcelExportStreamer::new);
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
LocalMacDevUtils.openFile(name);
}
}
/*******************************************************************************
** Imagine if we used the boldHeaderAndFooter styler
*******************************************************************************/
@Test
void runTableToXlsxWithOverrideStyles() throws Exception
{
ReportFormat format = ReportFormat.XLSX;
String name = "/tmp/report-fastexcel.xlsx";
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData reportMetaData = defineTableOnlyReport();
reportMetaData.getViews().get(0).withTitleFormat("My Title");
qInstance.addReport(reportMetaData);
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportDestination(new ReportDestination().withReportFormat(format).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
reportInput.setOverrideExportStreamerSupplier(() -> new ExcelPoiBasedStreamingExportStreamer()
{
@Override
protected PoiExcelStylerInterface getStylerInterface()
{
return new BoldHeaderAndFooterPoiExcelStyler();
}
});
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
LocalMacDevUtils.openFile(name);
}
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void runTableToXlsx() throws Exception
{
ReportFormat format = ReportFormat.XLSX;
String name = "/tmp/report-" + format + ".xlsx";
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(defineTableOnlyReport());
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportDestination(new ReportDestination().withReportFormat(format).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
LocalMacDevUtils.openFile(name);
}
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void runPivotToXlsx() throws Exception
{
String name = "/tmp/pivot-test.xlsx";
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(definePivotReport());
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportDestination(new ReportDestination().withReportFormat(ReportFormat.XLSX).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
LocalMacDevUtils.openFile(name, "/Applications/Numbers.app");
}
///////////////////////////////////////////////////////////
// read the file we wrote, and assert about its contents //
///////////////////////////////////////////////////////////
FileInputStream file = new FileInputStream(name);
XSSFWorkbook workbook = new XSSFWorkbook(file);
XSSFSheet sheet = workbook.getSheetAt(1);
List<XSSFPivotTable> pivotTables = sheet.getPivotTables();
XSSFPivotTable xssfPivotTable = pivotTables.get(0);
List<Integer> rowLabelColumns = xssfPivotTable.getRowLabelColumns();
List<Integer> colLabelColumns = xssfPivotTable.getColLabelColumns();
Sheet dataSheet = xssfPivotTable.getDataSheet();
Sheet parentSheet = xssfPivotTable.getParentSheet();
System.out.println();
Map<Integer, List<Object>> data = new HashMap<>();
int i = 0;
for(Row row : sheet)
{
data.put(i, new ArrayList<>());
for(Cell cell : row)
{
data.get(i).add(switch(cell.getCellType())
{
case _NONE -> "<_NONE>";
case NUMERIC -> cell.getNumericCellValue();
case STRING -> cell.getStringCellValue();
case FORMULA -> cell.getCellFormula();
case BLANK -> "<BLANK>";
case BOOLEAN -> cell.getBooleanCellValue();
case ERROR -> cell.getErrorCellValue();
});
}
i++;
}
System.out.println(data);
}
/*******************************************************************************
**
*******************************************************************************/
@ -369,8 +550,7 @@ public class GenerateReportActionTest extends BaseTest
{
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportFormat(ReportFormat.LIST_OF_MAPS);
reportInput.setReportOutputStream(new ByteArrayOutputStream());
reportInput.setReportDestination(new ReportDestination().withReportFormat(ReportFormat.LIST_OF_MAPS).withReportOutputStream(new ByteArrayOutputStream()));
reportInput.setInputValues(inputValues);
new GenerateReportAction().execute(reportInput);
}
@ -380,15 +560,15 @@ public class GenerateReportActionTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
private void insertPersonRecords(QInstance qInstance) throws QException
public static void insertPersonRecords(QInstance qInstance) throws QException
{
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY), List.of(
new PersonQRecord().withLastName("Jonson").withBirthDate(LocalDate.of(1980, Month.JANUARY, 31)).withNoOfShoes(null).withHomeStateId(1).withPrice(null).withCost(new BigDecimal("0.50")), // wrong last initial
new PersonQRecord().withLastName("Jones").withBirthDate(LocalDate.of(1980, Month.JANUARY, 31)).withNoOfShoes(3).withHomeStateId(1).withPrice(new BigDecimal("1.00")).withCost(new BigDecimal("0.50")), // wrong last initial
new PersonQRecord().withLastName("Kelly").withBirthDate(LocalDate.of(1979, Month.DECEMBER, 30)).withNoOfShoes(4).withHomeStateId(1).withPrice(new BigDecimal("1.20")).withCost(new BigDecimal("0.50")), // bad birthdate
new PersonQRecord().withLastName("Keller").withBirthDate(LocalDate.of(1980, Month.JANUARY, 7)).withNoOfShoes(5).withHomeStateId(1).withPrice(new BigDecimal("2.40")).withCost(new BigDecimal("3.50")),
new PersonQRecord().withLastName("Kelkhoff").withBirthDate(LocalDate.of(1980, Month.FEBRUARY, 15)).withNoOfShoes(6).withHomeStateId(1).withPrice(new BigDecimal("3.60")).withCost(new BigDecimal("3.50")),
new PersonQRecord().withLastName("Kelkhoff").withBirthDate(LocalDate.of(1980, Month.MARCH, 20)).withNoOfShoes(7).withHomeStateId(2).withPrice(new BigDecimal("4.80")).withCost(new BigDecimal("3.50"))
new PersonQRecord().withFirstName("Darin").withLastName("Jonson").withBirthDate(LocalDate.of(1980, Month.JANUARY, 31)).withNoOfShoes(null).withHomeStateId(1).withPrice(null).withCost(new BigDecimal("0.50")), // wrong last initial
new PersonQRecord().withFirstName("Darin").withLastName("Jones").withBirthDate(LocalDate.of(1980, Month.JANUARY, 31)).withNoOfShoes(3).withHomeStateId(1).withPrice(new BigDecimal("1.00")).withCost(new BigDecimal("0.50")), // wrong last initial
new PersonQRecord().withFirstName("Darin").withLastName("Kelly").withBirthDate(LocalDate.of(1979, Month.DECEMBER, 30)).withNoOfShoes(4).withHomeStateId(1).withPrice(new BigDecimal("1.20")).withCost(new BigDecimal("0.50")), // bad birthdate
new PersonQRecord().withFirstName("Trevor").withLastName("Keller").withBirthDate(LocalDate.of(1980, Month.JANUARY, 7)).withNoOfShoes(5).withHomeStateId(1).withPrice(new BigDecimal("2.40")).withCost(new BigDecimal("3.50")),
new PersonQRecord().withFirstName("Trevor").withLastName("Kelkhoff").withBirthDate(LocalDate.of(1980, Month.FEBRUARY, 15)).withNoOfShoes(6).withHomeStateId(1).withPrice(new BigDecimal("3.60")).withCost(new BigDecimal("3.50")),
new PersonQRecord().withFirstName("Kelly").withLastName("Kelkhoff").withBirthDate(LocalDate.of(1980, Month.MARCH, 20)).withNoOfShoes(7).withHomeStateId(2).withPrice(new BigDecimal("4.80")).withCost(new BigDecimal("3.50"))
));
}
@ -397,7 +577,7 @@ public class GenerateReportActionTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
public static QReportMetaData definePersonShoesPivotReport(boolean includeTotalRow)
public static QReportMetaData definePersonShoesSummaryReport(boolean includeTotalRow)
{
return new QReportMetaData()
.withName(REPORT_NAME)
@ -416,11 +596,11 @@ public class GenerateReportActionTest extends BaseTest
))
.withViews(List.of(
new QReportView()
.withName("pivot")
.withLabel("pivot")
.withName("summary")
.withLabel("summary")
.withDataSourceName("persons")
.withType(ReportType.SUMMARY)
.withPivotFields(List.of("lastName"))
.withSummaryFields(List.of("lastName"))
.withIncludeTotalRow(includeTotalRow)
.withTitleFormat("Number of shoes - people born between %s and %s - pivot on LastName, sort by Quantity, Revenue DESC")
.withTitleFields(List.of("${input.startDate}", "${input.endDate}"))
@ -452,39 +632,14 @@ public class GenerateReportActionTest extends BaseTest
@Test
void testTableOnlyReport() throws QException
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = new QReportMetaData()
.withName(REPORT_NAME)
.withDataSources(List.of(
new QReportDataSource()
.withName("persons")
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("birthDate", QCriteriaOperator.GREATER_THAN, List.of("${input.startDate}")))
)
))
.withInputFields(List.of(
new QFieldMetaData("startDate", QFieldType.DATE_TIME)
))
.withViews(List.of(
new QReportView()
.withName("table1")
.withLabel("table1")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
new QReportField().withName("id"),
new QReportField().withName("firstName"),
new QReportField().withName("lastName")
))
));
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = defineTableOnlyReport();
qInstance.addReport(report);
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1)));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("table1");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("Table 1");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(5, list.size());
@ -493,6 +648,88 @@ public class GenerateReportActionTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
private static QReportMetaData defineTableOnlyReport()
{
QReportMetaData report = new QReportMetaData()
.withName(REPORT_NAME)
.withDataSources(List.of(
new QReportDataSource()
.withName("persons")
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("birthDate", QCriteriaOperator.GREATER_THAN, List.of("${input.startDate}"))))))
.withInputFields(List.of(
new QFieldMetaData("startDate", QFieldType.DATE_TIME)))
.withViews(List.of(
new QReportView()
.withName("table1")
.withLabel("Table 1")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
new QReportField().withName("id"),
new QReportField().withName("firstName"),
new QReportField().withName("lastName")))));
return report;
}
/*******************************************************************************
**
*******************************************************************************/
private static QReportMetaData definePivotReport()
{
QReportMetaData report = new QReportMetaData()
.withName(REPORT_NAME)
.withDataSources(List.of(
new QReportDataSource()
.withName("persons")
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("birthDate", QCriteriaOperator.GREATER_THAN, List.of("${input.startDate}"))))))
.withInputFields(List.of(
new QFieldMetaData("startDate", QFieldType.DATE_TIME)))
.withViews(List.of(
new QReportView()
.withName("table1")
.withLabel("Table 1")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
new QReportField().withName("id"),
new QReportField().withName("firstName"),
new QReportField().withName("lastName"),
new QReportField().withName("homeStateId")
)),
new QReportView()
.withName("pivotTable1")
.withLabel("My Pivot Table")
.withType(ReportType.PIVOT)
.withPivotTableSourceViewName("table1")
.withPivotTableDefinition(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("homeStateId"))
// .withRow(new PivotTableGroupBy().withFieldName("lastName"))
// .withColumn(new PivotTableGroupBy().withFieldName("firstName"))
.withValue(new PivotTableValue().withFunction(PivotTableFunction.COUNT).withFieldName("id")))
));
return report;
}
/*******************************************************************************
**
*******************************************************************************/
@ -500,6 +737,180 @@ public class GenerateReportActionTest extends BaseTest
void testTwoTableViewsOneDataSourceReport() throws QException
{
QInstance qInstance = QContext.getQInstance();
defineTwoViewsOneDataSourceReport(qInstance);
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1)));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("Table 1");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(5, list.size());
assertThat(row).containsOnlyKeys("Id", "First Name", "Last Name");
list = ListOfMapsExportStreamer.getList("Table 2");
iterator = list.iterator();
row = iterator.next();
assertEquals(5, list.size());
assertThat(row).containsOnlyKeys("Birth Date");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testOneTableViewsOneDataSourceJsonReport() throws Exception
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = defineTableOnlyReport();
qInstance.addReport(report);
insertPersonRecords(qInstance);
String json = runToString(ReportFormat.JSON, report.getName());
// System.out.println(json);
/////////////////////////////////////////////////////////////////////////////////
// for a one-view report, we should just have an array of the report's records //
/////////////////////////////////////////////////////////////////////////////////
JSONArray jsonArray = new JSONArray(json);
assertEquals(6, jsonArray.length());
assertThat(jsonArray.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("id", 1)
.hasFieldOrPropertyWithValue("firstName", "Darin")
.hasFieldOrPropertyWithValue("lastName", "Jonson");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTwoTableViewsOneDataSourceJsonReport() throws Exception
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = defineTwoViewsOneDataSourceReport(qInstance);
insertPersonRecords(qInstance);
String json = runToString(ReportFormat.JSON, report.getName());
// System.out.println(json);
/////////////////////////////////////////////////////////////////////////////////
// for a multi-view report, we should have an array with the views as elements //
/////////////////////////////////////////////////////////////////////////////////
JSONArray jsonArray = new JSONArray(json);
assertEquals(2, jsonArray.length());
JSONObject firstView = jsonArray.getJSONObject(0);
assertEquals("Table 1", firstView.getString("name"));
JSONArray firstViewData = firstView.getJSONArray("data");
assertEquals(6, firstViewData.length());
assertThat(firstViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("id", 1)
.hasFieldOrPropertyWithValue("firstName", "Darin")
.hasFieldOrPropertyWithValue("lastName", "Jonson");
JSONObject secondView = jsonArray.getJSONObject(1);
assertEquals("Table 2", secondView.getString("name"));
JSONArray secondViewData = secondView.getJSONArray("data");
assertEquals(6, secondViewData.length());
assertThat(secondViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("birthDate", "1980-01-31");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTableViewsAndSummaryViewJsonReport() throws Exception
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = defineSimplePersonTableAndSummaryByFirstNameReport();
qInstance.addReport(report);
insertPersonRecords(qInstance);
String json = runToString(ReportFormat.JSON, report.getName());
System.out.println(json);
/////////////////////////////////////////////////////////////////////////////////
// for a multi-view report, we should have an array with the views as elements //
/////////////////////////////////////////////////////////////////////////////////
JSONArray jsonArray = new JSONArray(json);
assertEquals(2, jsonArray.length());
JSONObject firstView = jsonArray.getJSONObject(0);
assertEquals("Table 1", firstView.getString("name"));
JSONArray firstViewData = firstView.getJSONArray("data");
assertEquals(6, firstViewData.length());
assertThat(firstViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("id", 1)
.hasFieldOrPropertyWithValue("firstName", "Darin")
.hasFieldOrPropertyWithValue("lastName", "Jonson");
JSONObject secondView = jsonArray.getJSONObject(1);
assertEquals("Summary", secondView.getString("name"));
JSONArray secondViewData = secondView.getJSONArray("data");
assertEquals(4, secondViewData.length());
assertThat(secondViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("firstName", "Darin")
.hasFieldOrPropertyWithValue("personCount", 3);
assertThat(secondViewData.getJSONObject(3).toMap())
.hasFieldOrPropertyWithValue("firstName", "Totals")
.hasFieldOrPropertyWithValue("personCount", 6);
}
/*******************************************************************************
**
*******************************************************************************/
public static QReportMetaData defineSimplePersonTableAndSummaryByFirstNameReport()
{
return new QReportMetaData()
.withName(REPORT_NAME)
.withDataSources(List.of(
new QReportDataSource()
.withName("persons")
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilter(new QQueryFilter())
))
.withViews(List.of(
new QReportView()
.withName("table1")
.withLabel("Table 1")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
new QReportField().withName("id"),
new QReportField().withName("firstName"),
new QReportField().withName("lastName"),
new QReportField().withName("homeStateId")
)),
new QReportView()
.withName("summary")
.withLabel("Summary")
.withDataSourceName("persons")
.withType(ReportType.SUMMARY)
.withSummaryFields(List.of("firstName"))
.withIncludeTotalRow(true)
.withOrderByFields(List.of(new QFilterOrderBy("personCount", false)))
.withColumns(List.of(
new QReportField().withName("personCount").withLabel("Person Count").withFormula("${pivot.count.id}").withDisplayFormat(DisplayFormat.COMMAS)
))
));
}
/*******************************************************************************
**
*******************************************************************************/
private static QReportMetaData defineTwoViewsOneDataSourceReport(QInstance qInstance)
{
QReportMetaData report = new QReportMetaData()
.withName(REPORT_NAME)
.withDataSources(List.of(
@ -516,7 +927,7 @@ public class GenerateReportActionTest extends BaseTest
.withViews(List.of(
new QReportView()
.withName("table1")
.withLabel("table1")
.withLabel("Table 1")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
@ -526,7 +937,7 @@ public class GenerateReportActionTest extends BaseTest
)),
new QReportView()
.withName("table2")
.withLabel("table2")
.withLabel("Table 2")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
@ -535,21 +946,7 @@ public class GenerateReportActionTest extends BaseTest
));
qInstance.addReport(report);
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1)));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("table1");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(5, list.size());
assertThat(row).containsOnlyKeys("Id", "First Name", "Last Name");
list = ListOfMapsExportStreamer.getList("table2");
iterator = list.iterator();
row = iterator.next();
assertEquals(5, list.size());
assertThat(row).containsOnlyKeys("Birth Date");
return (report);
}
@ -566,8 +963,7 @@ public class GenerateReportActionTest extends BaseTest
ReportInput reportInput = new ReportInput();
reportInput.setReportName(TestUtils.REPORT_NAME_PERSON_SIMPLE);
reportInput.setReportFormat(ReportFormat.LIST_OF_MAPS);
reportInput.setReportOutputStream(new ByteArrayOutputStream());
reportInput.setReportDestination(new ReportDestination().withReportFormat(ReportFormat.LIST_OF_MAPS).withReportOutputStream(new ByteArrayOutputStream()));
new GenerateReportAction().execute(reportInput);
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("Simple Report");

View File

@ -0,0 +1,54 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.backend.core.actions.reporting;
import java.util.function.Function;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Unit test for JsonExportStreamer
*******************************************************************************/
class JsonExportStreamerTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test()
{
Function<String, String> runOne = label -> new JsonExportStreamer().getLabelForJson(new QFieldMetaData("test", QFieldType.STRING).withLabel(label));
assertEquals("sku", runOne.apply("SKU"));
assertEquals("clientName", runOne.apply("Client Name"));
assertEquals("slaStatus", runOne.apply("SLA Status"));
assertEquals("lineItem:sku", runOne.apply("Line Item: SKU"));
assertEquals("parcel:slaStatus", runOne.apply("Parcel: SLA Status"));
assertEquals("order:client", runOne.apply("Order: Client"));
}
}

View File

@ -43,7 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
/*******************************************************************************
** Unit test for ReplaceAction
** Unit test for ReplaceAction
*******************************************************************************/
class ReplaceActionTest extends BaseTest
{
@ -157,6 +157,134 @@ class ReplaceActionTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTwoKeysWithNullsNotMatchingAllowingDelete() throws QException
{
String tableName = TestUtils.TABLE_NAME_TWO_KEYS;
////////////////////////////////
// start with these 2 records //
////////////////////////////////
new InsertAction().execute(new InsertInput(tableName).withRecords(List.of(
new QRecord().withValue("key1", 1).withValue("key2", 2),
new QRecord().withValue("key1", 3)
)));
////////////////////////////////////////////////////
// now do a replace action that just updates them //
////////////////////////////////////////////////////
List<QRecord> newThings = List.of(
new QRecord().withValue("key1", 1).withValue("key2", 2),
new QRecord().withValue("key1", 3)
);
//////////////////////////////
// replace allowing deletes //
//////////////////////////////
ReplaceInput replaceInput = new ReplaceInput();
replaceInput.setTableName(tableName);
replaceInput.setKey(new UniqueKey("key1", "key2"));
replaceInput.setOmitDmlAudit(true);
replaceInput.setRecords(newThings);
replaceInput.setFilter(null);
ReplaceOutput replaceOutput = new ReplaceAction().execute(replaceInput);
assertEquals(1, replaceOutput.getInsertOutput().getRecords().size());
assertEquals(1, replaceOutput.getUpdateOutput().getRecords().size());
assertEquals(1, replaceOutput.getDeleteOutput().getDeletedRecordCount());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTwoKeysWithNullsNotMatchingNotAllowingDelete() throws QException
{
String tableName = TestUtils.TABLE_NAME_TWO_KEYS;
////////////////////////////////
// start with these 2 records //
////////////////////////////////
new InsertAction().execute(new InsertInput(tableName).withRecords(List.of(
new QRecord().withValue("key1", 1).withValue("key2", 2),
new QRecord().withValue("key1", 3)
)));
////////////////////////////////////////////////////
// now do a replace action that just updates them //
////////////////////////////////////////////////////
List<QRecord> newThings = List.of(
new QRecord().withValue("key1", 1).withValue("key2", 2),
new QRecord().withValue("key1", 3)
);
/////////////////////////////////
// replace disallowing deletes //
/////////////////////////////////
ReplaceInput replaceInput = new ReplaceInput();
replaceInput.setTableName(tableName);
replaceInput.setKey(new UniqueKey("key1", "key2"));
replaceInput.setOmitDmlAudit(true);
replaceInput.setRecords(newThings);
replaceInput.setFilter(null);
replaceInput.setPerformDeletes(false);
ReplaceOutput replaceOutput = new ReplaceAction().execute(replaceInput);
assertEquals(1, replaceOutput.getInsertOutput().getRecords().size());
assertEquals(1, replaceOutput.getUpdateOutput().getRecords().size());
assertNull(replaceOutput.getDeleteOutput());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTwoKeysWithNullMatching() throws QException
{
String tableName = TestUtils.TABLE_NAME_TWO_KEYS;
////////////////////////////////
// start with these 2 records //
////////////////////////////////
new InsertAction().execute(new InsertInput(tableName).withRecords(List.of(
new QRecord().withValue("key1", 1).withValue("key2", 2),
new QRecord().withValue("key1", 3)
)));
////////////////////////////////////////////////////
// now do a replace action that just updates them //
////////////////////////////////////////////////////
List<QRecord> newThings = List.of(
new QRecord().withValue("key1", 1).withValue("key2", 2),
new QRecord().withValue("key1", 3)
);
///////////////////////////////////////////////
// replace treating null key values as equal //
///////////////////////////////////////////////
ReplaceInput replaceInput = new ReplaceInput();
replaceInput.setTableName(tableName);
replaceInput.setKey(new UniqueKey("key1", "key2"));
replaceInput.setOmitDmlAudit(true);
replaceInput.setRecords(newThings);
replaceInput.setFilter(null);
replaceInput.setAllowNullKeyValuesToEqual(true);
ReplaceOutput replaceOutput = new ReplaceAction().execute(replaceInput);
assertEquals(0, replaceOutput.getInsertOutput().getRecords().size());
assertEquals(2, replaceOutput.getUpdateOutput().getRecords().size());
assertEquals(0, replaceOutput.getDeleteOutput().getDeletedRecordCount());
}
/*******************************************************************************
**
*******************************************************************************/
@ -297,4 +425,4 @@ class ReplaceActionTest extends BaseTest
return new GetAction().executeForRecord(new GetInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withUniqueKey(Map.of("firstName", firstName, "lastName", lastName))).getValueInteger("noOfShoes");
}
}
}

View File

@ -35,6 +35,7 @@ import com.kingsrook.qqq.backend.core.model.actions.templates.RenderTemplateInpu
import com.kingsrook.qqq.backend.core.model.actions.templates.RenderTemplateOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.templates.TemplateType;
import com.kingsrook.qqq.backend.core.utils.LocalMacDevUtils;
import org.junit.jupiter.api.Test;
@ -107,8 +108,8 @@ class ConvertHtmlToPdfActionTest extends BaseTest
/////////////////////////////////////////////////////////////////////////
// for local dev on a mac, turn this on to auto-open the generated PDF //
/////////////////////////////////////////////////////////////////////////
// todo not commit
// Runtime.getRuntime().exec(new String[] { "/usr/bin/open", "/tmp/file.pdf" });
// LocalMacDevUtils.mayOpenFiles = true;
LocalMacDevUtils.openFile("/tmp/file.pdf");
}
}

View File

@ -216,7 +216,7 @@ class QInstanceHelpContentManagerTest extends BaseTest
// now - post-insert customizer should have automatically added help content to the instance //
///////////////////////////////////////////////////////////////////////////////////////////////
assertTrue(widget.getHelpContent().containsKey("label"));
assertEquals("i need somebody", widget.getHelpContent().get("label").getContent());
assertEquals("i need somebody", widget.getHelpContent().get("label").get(0).getContent());
}

View File

@ -272,6 +272,25 @@ public class QInstanceValidatorTest extends BaseTest
/*******************************************************************************
** Test rules for process step names (must be set; must not be duplicated)
**
*******************************************************************************/
@Test
public void test_validateProcessStepNames()
{
assertValidationFailureReasons((qInstance) -> qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE_INTERACTIVE).getStepList().get(0).setName(null),
"Missing name for a step at index");
assertValidationFailureReasons((qInstance) -> qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE_INTERACTIVE).getStepList().get(0).setName(""),
"Missing name for a step at index");
assertValidationFailureReasons((qInstance) -> qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE_INTERACTIVE).getStepList().forEach(s -> s.setName("myStep")),
"Duplicate step name [myStep]", "Duplicate step name [myStep]");
}
/*******************************************************************************
** Test that a process with a step that is a private class fails
**

View File

@ -36,6 +36,7 @@ import com.kingsrook.qqq.backend.core.utils.TestUtils;
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.assertNull;
/*******************************************************************************
@ -101,6 +102,48 @@ class DateTimeDisplayValueBehaviorTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testBadZoneIdFromOtherField()
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
table.withField(new QFieldMetaData("timeZone", QFieldType.STRING));
table.getField("createDate").withBehavior(new DateTimeDisplayValueBehavior().withZoneIdFromFieldName("timeZone"));
QRecord record = new QRecord().withValue("createDate", Instant.parse("2024-04-04T19:12:00Z")).withValue("timeZone", "fail");
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.FORMATTING, qInstance, table, List.of(record), null);
assertNull(record.getDisplayValue("createDate"));
record = new QRecord().withValue("createDate", Instant.parse("2024-04-04T19:12:00Z")).withValue("timeZone", null);
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.FORMATTING, qInstance, table, List.of(record), null);
assertNull(record.getDisplayValue("createDate"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testNullValue()
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
table.withField(new QFieldMetaData("timeZone", QFieldType.STRING));
table.getField("createDate").withBehavior(new DateTimeDisplayValueBehavior().withZoneIdFromFieldName("timeZone"));
QRecord record = new QRecord().withValue("createDate", null).withValue("timeZone", "UTC");
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.FORMATTING, qInstance, table, List.of(record), null);
assertNull(record.getDisplayValue("createDate"));
}
/*******************************************************************************
**
*******************************************************************************/
@ -108,8 +151,8 @@ class DateTimeDisplayValueBehaviorTest extends BaseTest
void testValidation()
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
QFieldMetaData field = table.getField("createDate");
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
QFieldMetaData field = table.getField("createDate");
table.withField(new QFieldMetaData("timeZone", QFieldType.STRING));
Function<Consumer<DateTimeDisplayValueBehavior>, List<String>> testOne = setup ->

View File

@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
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.assertNull;
@ -156,4 +157,47 @@ class DynamicDefaultValueBehaviorTest extends BaseTest
assertNull(record.getValue("firstName"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testUserId()
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
table.getField("firstName").withBehavior(DynamicDefaultValueBehavior.USER_ID);
{
////////////////////////////////
// set it (if null) on insert //
////////////////////////////////
QRecord record = new QRecord().withValue("id", 1);
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, qInstance, table, List.of(record), null);
assertEquals(QContext.getQSession().getUser().getIdReference(), record.getValue("firstName"));
}
{
////////////////////////////////
// set it (if null) on update //
////////////////////////////////
QRecord record = new QRecord().withValue("id", 1);
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.UPDATE, qInstance, table, List.of(record), null);
assertEquals(QContext.getQSession().getUser().getIdReference(), record.getValue("firstName"));
}
{
////////////////////////////////////////////////////////////////////
// only set it if it wasn't previously set (both insert & update) //
////////////////////////////////////////////////////////////////////
QRecord record = new QRecord().withValue("id", 1).withValue("firstName", "Bob");
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, qInstance, table, List.of(record), null);
assertEquals("Bob", record.getValue("firstName"));
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.UPDATE, qInstance, table, List.of(record), null);
assertEquals("Bob", record.getValue("firstName"));
}
}
}

View File

@ -0,0 +1,166 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.backend.core.model.savedreports;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
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.assertNull;
/*******************************************************************************
** Unit test for SavedReportJsonFieldDisplayValueFormatter
*******************************************************************************/
class SavedReportJsonFieldDisplayValueFormatterTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void beforeEach() throws QException
{
QContext.getQInstance().add(new SavedReportsMetaDataProvider().defineSavedReportTable(TestUtils.MEMORY_BACKEND_NAME, null));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPostQuery() throws QException
{
UnsafeFunction<SavedReport, QRecord, QException> customize = savedReport ->
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(SavedReport.TABLE_NAME);
QRecord record = savedReport.toQRecord();
for(String fieldName : List.of("queryFilterJson", "columnsJson", "pivotTableJson"))
{
SavedReportJsonFieldDisplayValueFormatter.getInstance().apply(ValueBehaviorApplier.Action.FORMATTING, List.of(record), qInstance, table, table.getField(fieldName));
}
return (record);
};
{
QRecord record = customize.apply(new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withColumnsJson(JsonUtils.toJson(new ReportColumns()))
.withPivotTableJson(JsonUtils.toJson(new PivotTableDefinition())));
assertEquals("0 Filters", record.getDisplayValue("queryFilterJson"));
assertEquals("0 Columns", record.getDisplayValue("columnsJson"));
assertEquals("0 Rows, 0 Columns, and 0 Values", record.getDisplayValue("pivotTableJson"));
}
{
QRecord record = customize.apply(new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withColumnsJson(JsonUtils.toJson(new ReportColumns())));
assertEquals("0 Filters", record.getDisplayValue("queryFilterJson"));
assertEquals("0 Columns", record.getDisplayValue("columnsJson"));
assertNull(record.getDisplayValue("pivotTableJson"));
}
{
QRecord record = customize.apply(new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.IS_NOT_BLANK))))
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
.withColumn(new ReportColumn().withName("birthDate"))))
.withPivotTableJson(JsonUtils.toJson(new PivotTableDefinition()
.withRow(new PivotTableGroupBy())
.withValue(new PivotTableValue())
)));
assertEquals("1 Filter", record.getDisplayValue("queryFilterJson"));
assertEquals("1 Column", record.getDisplayValue("columnsJson"));
assertEquals("1 Row, 0 Columns, and 1 Value", record.getDisplayValue("pivotTableJson"));
}
{
QRecord record = customize.apply(new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()
.withCriteria(new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN, 1))
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.IS_NOT_BLANK))))
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
.withColumn(new ReportColumn().withName("__check__").withIsVisible(true))
.withColumn(new ReportColumn().withName("id"))
.withColumn(new ReportColumn().withName("firstName").withIsVisible(true))
.withColumn(new ReportColumn().withName("lastName").withIsVisible(false))
.withColumn(new ReportColumn().withName("birthDate"))))
.withPivotTableJson(JsonUtils.toJson(new PivotTableDefinition()
.withRow(new PivotTableGroupBy())
.withRow(new PivotTableGroupBy())
.withColumn(new PivotTableGroupBy())
.withValue(new PivotTableValue())
.withValue(new PivotTableValue())
.withValue(new PivotTableValue())
)));
assertEquals("2 Filters", record.getDisplayValue("queryFilterJson"));
assertEquals("3 Columns", record.getDisplayValue("columnsJson"));
assertEquals("2 Rows, 1 Column, and 3 Values", record.getDisplayValue("pivotTableJson"));
}
{
QRecord record = customize.apply(new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson("blah")
.withColumnsJson("<xml?>")
.withPivotTableJson("{]"));
assertEquals("Invalid Filter...", record.getDisplayValue("queryFilterJson"));
assertEquals("Invalid Columns...", record.getDisplayValue("columnsJson"));
assertEquals("Invalid Pivot Table...", record.getDisplayValue("pivotTableJson"));
}
}
}

View File

@ -0,0 +1,228 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.backend.core.model.savedreports;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableFunction;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Unit test for SavedReportTableCustomizer
*******************************************************************************/
class SavedReportTableCustomizerTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void beforeEach() throws QException
{
QContext.getQInstance().add(new SavedReportsMetaDataProvider().defineSavedReportTable(TestUtils.MEMORY_BACKEND_NAME, null));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPreInsertAndPreUpdateAreWired() throws QException
{
SavedReport badRecord = new SavedReport()
.withLabel("My Report")
.withTableName("notATable");
/////////////////////////////////////////////////////////////////////
// assertions to apply both to a failed insert and a failed update //
/////////////////////////////////////////////////////////////////////
Consumer<QRecord> asserter = record -> assertThat(record.getErrors())
.hasSizeGreaterThanOrEqualTo(2)
.anyMatch(e -> e.getMessage().contains("Unrecognized table name"))
.anyMatch(e -> e.getMessage().contains("must contain at least 1 column"));
////////////////////////////////////////////////////////////
// go through insert action, to ensure wired-up correctly //
////////////////////////////////////////////////////////////
InsertOutput insertOutput = new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(badRecord));
asserter.accept(insertOutput.getRecords().get(0));
////////////////////////////////
// likewise for update action //
////////////////////////////////
UpdateOutput updateOutput = new UpdateAction().execute(new UpdateInput(SavedReport.TABLE_NAME).withRecordEntity(badRecord));
asserter.accept(updateOutput.getRecords().get(0));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testParseFails()
{
QRecord record = new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson("...")
.withColumnsJson("x")
.withPivotTableJson("[")
.toQRecord();
new SavedReportTableCustomizer().preValidateRecord(record);
assertThat(record.getErrors())
.hasSize(3)
.anyMatch(e -> e.getMessage().contains("Unable to parse queryFilterJson"))
.anyMatch(e -> e.getMessage().contains("Unable to parse columnsJson"))
.anyMatch(e -> e.getMessage().contains("Unable to parse pivotTableJson"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testNoColumns()
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// given a reportColumns object, serialize it to json, put it in a saved report record, and run the pre-validator //
// then assert we got error saying there were no columns. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Consumer<ReportColumns> asserter = reportColumns ->
{
SavedReport savedReport = new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withColumnsJson(JsonUtils.toJson(reportColumns));
QRecord record = savedReport.toQRecord();
new SavedReportTableCustomizer().preValidateRecord(record);
assertThat(record.getErrors())
.hasSize(1)
.anyMatch(e -> e.getMessage().contains("must contain at least 1 column"));
};
asserter.accept(new ReportColumns());
asserter.accept(new ReportColumns().withColumns(null));
asserter.accept(new ReportColumns().withColumns(new ArrayList<>()));
asserter.accept(new ReportColumns().withColumn(new ReportColumn()
.withName("id").withIsVisible(false)));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotTables()
{
BiConsumer<PivotTableDefinition, List<String>> asserter = (PivotTableDefinition ptd, List<String> expectedAnyMessageToContain) ->
{
SavedReport savedReport = new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
.withColumn("id")
.withColumn("firstName")
.withColumn("lastName")
.withColumn("birthDate")))
.withPivotTableJson(JsonUtils.toJson(ptd));
QRecord record = savedReport.toQRecord();
new SavedReportTableCustomizer().preValidateRecord(record);
assertThat(record.getErrors()).hasSize(expectedAnyMessageToContain.size());
for(String expected : expectedAnyMessageToContain)
{
assertThat(record.getErrors())
.anyMatch(e -> e.getMessage().contains(expected));
}
};
asserter.accept(new PivotTableDefinition(), List.of("must contain at least 1 row"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withRow(new PivotTableGroupBy()),
List.of("Missing field name for at least one pivot table row"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withRow(new PivotTableGroupBy().withFieldName("createDate")),
List.of("row is using field (Create Date) which is not an active column"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withColumn(new PivotTableGroupBy()),
List.of("Missing field name for at least one pivot table column"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withColumn(new PivotTableGroupBy().withFieldName("createDate")),
List.of("column is using field (Create Date) which is not an active column"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withValue(new PivotTableValue().withFunction(PivotTableFunction.SUM)),
List.of("Missing field name for at least one pivot table value"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withValue(new PivotTableValue().withFieldName("createDate").withFunction(PivotTableFunction.SUM)),
List.of("value is using field (Create Date) which is not an active column"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withValue(new PivotTableValue().withFieldName("firstName")),
List.of("Missing function for at least one pivot table value"));
}
}

View File

@ -45,6 +45,7 @@ import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobType;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobsMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.scheduler.QScheduleManager;
import com.kingsrook.qqq.backend.core.scheduler.SchedulerTestUtils;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzJobAndTriggerWrapper;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzTestUtils;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -97,7 +98,7 @@ class ScheduledJobTableCustomizerTest extends BaseTest
@AfterEach
void afterEach()
{
QuartzTestUtils.afterEach();
SchedulerTestUtils.afterEach();
}

View File

@ -89,7 +89,7 @@ class EnumerationCountActionTest extends BaseTest
QInstance instance = QContext.getQInstance();
instance.addBackend(new QBackendMetaData()
.withName("enum")
.withBackendType("enum")
.withBackendType(EnumerationBackendModule.class)
);
instance.addTable(new QTableMetaData()

View File

@ -167,7 +167,7 @@ class EnumerationQueryActionTest extends BaseTest
QInstance instance = QContext.getQInstance();
instance.addBackend(new QBackendMetaData()
.withName("enum")
.withBackendType("enum")
.withBackendType(EnumerationBackendModule.class)
);
instance.addTable(new QTableMetaData()

View File

@ -50,7 +50,7 @@ class BasicRunReportProcessTest extends BaseTest
void testRunReport() throws QException
{
QInstance instance = TestUtils.defineInstance();
QReportMetaData report = GenerateReportActionTest.definePersonShoesPivotReport(true);
QReportMetaData report = GenerateReportActionTest.definePersonShoesSummaryReport(true);
QProcessMetaData runReportProcess = BasicRunReportProcess.defineProcessMetaData();
instance.addReport(report);

View File

@ -0,0 +1,406 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.backend.core.processes.implementations.savedreports;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallbackFactory;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportActionTest;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.ReportFormatPossibleValueEnum;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableFunction;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.savedreports.ReportColumns;
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReportsMetaDataProvider;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryStorageAction;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.LocalMacDevUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Unit test for RenderSavedReportExecuteStep
*******************************************************************************/
class RenderSavedReportProcessTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void beforeEach() throws Exception
{
new SavedReportsMetaDataProvider().defineAll(QContext.getQInstance(), TestUtils.MEMORY_BACKEND_NAME, TestUtils.MEMORY_BACKEND_NAME, null);
GenerateReportActionTest.insertPersonRecords(QContext.getQInstance());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
@Disabled
void testForDevPrintAPivotDefinitionAsJson()
{
System.out.println(JsonUtils.toPrettyJson(new PivotTableDefinition()
.withRow(new PivotTableGroupBy()
.withFieldName("homeStateId"))
.withRow(new PivotTableGroupBy()
.withFieldName("firstName"))
.withValue(new PivotTableValue()
.withFieldName("id")
.withFunction(PivotTableFunction.COUNT))
.withValue(new PivotTableValue()
.withFieldName("cost")
.withFunction(PivotTableFunction.SUM))
));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTableOnlyReport() throws Exception
{
String label = "Test Report";
//////////////////////////////////////////////////////////////////////////////////////////
// do columns json as a string, rather than a toJson'ed ReportColumns object, //
// to help verify that we don't choke on un-recognized properties (e.g., as QFMD sends) //
//////////////////////////////////////////////////////////////////////////////////////////
String columnsJson = """
{"columns":[
{"name": "k"},
{"name": "id"},
{"name": "firstName", "isVisible": true},
{"name": "lastName", "pinned": "left"},
{"name": "createDate", "isVisible": false}
]}""";
QRecord savedReport = new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(new SavedReport()
.withLabel(label)
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withColumnsJson(columnsJson)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
)).getRecords().get(0);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.CSV);
String downloadFileName = runProcessOutput.getValueString("downloadFileName");
assertThat(downloadFileName)
.startsWith(label + " - ")
.matches(".*\\d\\d\\d\\d-\\d\\d-\\d\\d-\\d\\d\\d\\d.*")
.endsWith(".csv");
InputStream inputStream = getInputStream(runProcessOutput);
List<String> lines = IOUtils.readLines(inputStream, StandardCharsets.UTF_8);
assertEquals("""
"Id","First Name","Last Name"
""".trim(), lines.get(0));
assertEquals("""
"1","Darin","Jonson"
""".trim(), lines.get(1));
writeTmpFileAndOpen(inputStream, ".csv");
}
/*******************************************************************************
**
*******************************************************************************/
private static InputStream getInputStream(RunProcessOutput runProcessOutput) throws QException
{
String storageTableName = runProcessOutput.getValueString("storageTableName");
String storageReference = runProcessOutput.getValueString("storageReference");
InputStream inputStream = new MemoryStorageAction().getInputStream(new StorageInput(storageTableName).withReference(storageReference));
return inputStream;
}
/*******************************************************************************
**
*******************************************************************************/
private void writeTmpFileAndOpen(InputStream inputStream, String suffix) throws IOException
{
// LocalMacDevUtils.mayOpenFiles = true;
if(LocalMacDevUtils.mayOpenFiles)
{
inputStream.reset();
File tmpFile = File.createTempFile(getClass().getName(), suffix, new File("/tmp/"));
FileOutputStream fileOutputStream = new FileOutputStream(tmpFile);
inputStream.transferTo(fileOutputStream);
fileOutputStream.close();
LocalMacDevUtils.openFile(tmpFile.getAbsolutePath());
}
}
/*******************************************************************************
**
*******************************************************************************/
private QRecord insertBasicSavedPivotReport(String label) throws QException
{
return new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(new SavedReport()
.withLabel(label)
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
.withColumn("id")
.withColumn("firstName")
.withColumn("lastName")
.withColumn("cost")
.withColumn("birthDate")
.withColumn("homeStateId")))
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withPivotTableJson(JsonUtils.toJson(new PivotTableDefinition()
.withRow(new PivotTableGroupBy()
.withFieldName("homeStateId"))
.withRow(new PivotTableGroupBy()
.withFieldName("firstName"))
.withValue(new PivotTableValue()
.withFieldName("id")
.withFunction(PivotTableFunction.COUNT))
.withValue(new PivotTableValue()
.withFieldName("cost")
.withFunction(PivotTableFunction.SUM))
.withValue(new PivotTableValue()
.withFieldName("birthDate")
.withFunction(PivotTableFunction.MIN))
.withValue(new PivotTableValue()
.withFieldName("birthDate")
.withFunction(PivotTableFunction.MAX))
))
)).getRecords().get(0);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotXlsx() throws Exception
{
String label = "Test Pivot Report";
QRecord savedReport = insertBasicSavedPivotReport(label);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.XLSX);
InputStream inputStream = getInputStream(runProcessOutput);
writeTmpFileAndOpen(inputStream, ".xlsx");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotJson() throws Exception
{
String label = "Test Pivot Report JSON";
QRecord savedReport = insertBasicSavedPivotReport(label);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.JSON);
InputStream inputStream = getInputStream(runProcessOutput);
String json = StringUtils.join("\n", IOUtils.readLines(inputStream, StandardCharsets.UTF_8));
printReport(json);
JSONArray jsonArray = new JSONArray(json);
assertEquals(2, jsonArray.length());
JSONObject firstView = jsonArray.getJSONObject(0);
assertEquals(label, firstView.getString("name"));
JSONArray firstViewData = firstView.getJSONArray("data");
assertEquals(6, firstViewData.length());
assertThat(firstViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("id", 1)
.hasFieldOrPropertyWithValue("firstName", "Darin");
JSONObject pivotView = jsonArray.getJSONObject(1);
assertEquals("Pivot Table", pivotView.getString("name"));
JSONArray pivotViewData = pivotView.getJSONArray("data");
assertEquals(4, pivotViewData.length());
assertThat(pivotViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("homeState", "IL")
.hasFieldOrPropertyWithValue("firstName", "Darin")
.hasFieldOrPropertyWithValue("countOfId", 3)
.hasFieldOrPropertyWithValue("sumOfCost", new BigDecimal("1.50"));
assertThat(pivotViewData.getJSONObject(3).toMap())
.hasFieldOrPropertyWithValue("homeState", "Totals")
.hasFieldOrPropertyWithValue("countOfId", 6)
.hasFieldOrPropertyWithValue("sumOfCost", new BigDecimal("12.00"));
}
/*******************************************************************************
**
*******************************************************************************/
private void printReport(String report)
{
// System.out.println(report);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotCSV() throws Exception
{
String label = "Test Pivot Report CSV";
QRecord savedReport = insertBasicSavedPivotReport(label);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.CSV);
InputStream inputStream = getInputStream(runProcessOutput);
List<String> csv = IOUtils.readLines(inputStream, StandardCharsets.UTF_8);
System.out.println(csv);
assertEquals("""
"Home State","First Name","Count Of Id","Sum Of Cost","Min Of Birth Date","Max Of Birth Date\"""", csv.get(0));
assertEquals("""
"Totals","","6","12.00","1979-12-30","1980-03-20\"""", csv.get(4));
}
/*******************************************************************************
**
*******************************************************************************/
private QRecord insertSavedPivotReportWithAllFunctions(String label) throws QException
{
PivotTableDefinition pivotTableDefinition = new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("firstName"));
for(PivotTableFunction function : PivotTableFunction.values())
{
pivotTableDefinition.withValue(new PivotTableValue().withFieldName("cost").withFunction(function));
}
return new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(new SavedReport()
.withLabel(label)
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
.withColumn("id")
.withColumn("firstName")
.withColumn("cost")))
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withPivotTableJson(JsonUtils.toJson(pivotTableDefinition))
)).getRecords().get(0);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotXlsxAllFunctions() throws Exception
{
String label = "Test Pivot Report";
QRecord savedReport = insertSavedPivotReportWithAllFunctions(label);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.XLSX);
String serverFilePath = runProcessOutput.getValueString("serverFilePath");
System.out.println(serverFilePath);
InputStream inputStream = getInputStream(runProcessOutput);
writeTmpFileAndOpen(inputStream, ".xlsx");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotCSVAllFunctions() throws Exception
{
String label = "Test Pivot Report CSV";
QRecord savedReport = insertSavedPivotReportWithAllFunctions(label);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.CSV);
InputStream inputStream = getInputStream(runProcessOutput);
List<String> csv = IOUtils.readLines(inputStream, StandardCharsets.UTF_8);
System.out.println(csv);
assertEquals("""
"First Name","Average Of Cost","Count Of Cost","Count_nums Of Cost","Max Of Cost","Min Of Cost","Product Of Cost","Std_dev Of Cost","Std_devp Of Cost","Sum Of Cost","Var Of Cost","Varp Of Cost\"""", csv.get(0));
assertEquals("""
"Totals","2.0","6","6","3.50","0.50","5.359375000000","1.6432","1.5000","12.00","2.7000","2.2500\"""", csv.get(4));
}
/*******************************************************************************
**
*******************************************************************************/
private static RunProcessOutput runRenderReportProcess(QRecord savedReport, ReportFormatPossibleValueEnum reportFormat) throws QException
{
RunProcessInput input = new RunProcessInput();
input.setProcessName(RenderSavedReportMetaDataProducer.NAME);
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
input.setCallback(QProcessCallbackFactory.forRecord(savedReport));
input.addValue("reportFormat", reportFormat.getPossibleValueId());
RunProcessOutput runProcessOutput = new RunProcessAction().execute(input);
return runProcessOutput;
}
}

View File

@ -54,28 +54,7 @@ class QScheduleManagerTest extends BaseTest
void afterEach()
{
QLogger.deactivateCollectingLoggerForClass(QuartzScheduler.class);
try
{
QScheduleManager.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
try
{
QuartzScheduler.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
SchedulerTestUtils.afterEach();
}
@ -178,4 +157,4 @@ class QScheduleManagerTest extends BaseTest
.anyMatch(l -> l.getMessage().matches(".*Scheduled new job.*TABLE_AUTOMATIONS.scheduledJob:4.*"));
}
}
}

View File

@ -36,6 +36,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaDa
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJob;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobParameter;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobType;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
@ -86,6 +87,38 @@ public class SchedulerTestUtils
/*******************************************************************************
**
*******************************************************************************/
public static void afterEach()
{
try
{
QScheduleManager.getInstance().stop();
QScheduleManager.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
try
{
QuartzScheduler.getInstance().stop();
QuartzScheduler.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -58,28 +58,7 @@ class RescheduleAllJobsProcessTest extends BaseTest
void afterEach()
{
QLogger.deactivateCollectingLoggerForClass(QuartzScheduler.class);
try
{
QScheduleManager.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
try
{
QuartzScheduler.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
SchedulerTestUtils.afterEach();
}

View File

@ -0,0 +1,157 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.backend.core.scheduler.processes;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QCollectingLogger;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJob;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobType;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobsMetaDataProvider;
import com.kingsrook.qqq.backend.core.scheduler.QScheduleManager;
import com.kingsrook.qqq.backend.core.scheduler.SchedulerTestUtils;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzJobAndTriggerWrapper;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzTestUtils;
import com.kingsrook.qqq.backend.core.scheduler.schedulable.runner.SchedulableSQSQueueRunner;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.quartz.SchedulerException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for ScheduleAllNewJobsProcess
*******************************************************************************/
class ScheduleAllNewJobsProcessTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@AfterEach
void afterEach()
{
SchedulerTestUtils.afterEach();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException, SchedulerException
{
try
{
QCollectingLogger quartzSchedulerLog = QLogger.activateCollectingLoggerForClass(QuartzScheduler.class);
QInstance qInstance = QContext.getQInstance();
new ScheduledJobsMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance, ScheduleAllNewJobsProcess.class.getPackageName());
QuartzTestUtils.setupInstanceForQuartzTests();
///////////////////////////////////////////////////////////////////////////////////
// clear out the customizers that would normally schedule jobs as we insert them //
///////////////////////////////////////////////////////////////////////////////////
qInstance.getTable(ScheduledJob.TABLE_NAME).withCustomizers(Collections.emptyMap());
QScheduleManager qScheduleManager = QScheduleManager.initInstance(qInstance, () -> QContext.getQSession());
qScheduleManager.start();
QuartzScheduler quartzScheduler = QuartzScheduler.getInstance();
List<QuartzJobAndTriggerWrapper> wrappers = quartzScheduler.queryQuartz();
//////////////////////////////////////////////
// make sure nothing is scheduled initially //
//////////////////////////////////////////////
assertTrue(wrappers.isEmpty());
////////////////////////////////////////////////////////////////////////////
// insert a scheduled job - run schedule-new, make sure it gets scheduled //
////////////////////////////////////////////////////////////////////////////
new InsertAction().execute(new InsertInput(ScheduledJob.TABLE_NAME).withRecordEntity(SchedulerTestUtils
.newScheduledJob(ScheduledJobType.PROCESS, Map.of("processName", TestUtils.PROCESS_NAME_GREET_PEOPLE))
.withLabel("Test job 1")
.withId(null)
.withSchedulerName(QuartzTestUtils.QUARTZ_SCHEDULER_NAME)));
RunProcessInput input = new RunProcessInput();
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
input.setProcessName(ScheduleAllNewJobsProcess.class.getSimpleName());
new RunProcessAction().execute(input);
///////////////////////////////////////////////////////////////
// make sure our scheduledJob here got scheduled with quartz //
///////////////////////////////////////////////////////////////
wrappers = quartzScheduler.queryQuartz();
assertEquals(1, wrappers.size());
assertTrue(wrappers.stream().anyMatch(w -> w.jobDetail().getKey().getName().equals("scheduledJob:1")));
///////////////
// repeat it //
///////////////
new InsertAction().execute(new InsertInput(ScheduledJob.TABLE_NAME).withRecordEntity(SchedulerTestUtils
.newScheduledJob(ScheduledJobType.PROCESS, Map.of("processName", TestUtils.PROCESS_NAME_GREET_PEOPLE_INTERACTIVE))
.withLabel("Test job 2")
.withId(null)
.withSchedulerName(QuartzTestUtils.QUARTZ_SCHEDULER_NAME)));
input = new RunProcessInput();
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
input.setProcessName(ScheduleAllNewJobsProcess.class.getSimpleName());
new RunProcessAction().execute(input);
wrappers = quartzScheduler.queryQuartz();
assertEquals(2, wrappers.size());
assertTrue(wrappers.stream().anyMatch(w -> w.jobDetail().getKey().getName().equals("scheduledJob:2")));
/////////////////////////////////////////////////////////////////////////////////////
// make sure quartzScheduler never logged about deleting or re-scheduling anything //
/////////////////////////////////////////////////////////////////////////////////////
assertThat(quartzSchedulerLog.getCollectedMessages())
.noneMatch(m -> m.getMessage().toLowerCase().contains("delete"))
.noneMatch(m -> m.getMessage().toLowerCase().contains("re-schedule"));
}
finally
{
QLogger.deactivateCollectingLoggerForClass(SchedulableSQSQueueRunner.class);
}
}
}

View File

@ -28,7 +28,6 @@ import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
@ -40,6 +39,7 @@ import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzTestUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.quartz.SchedulerException;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -52,35 +52,23 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class UnscheduleAllJobsProcessTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void beforeEach()
{
SchedulerTestUtils.afterEach();
}
/*******************************************************************************
**
*******************************************************************************/
@AfterEach
void afterEach()
{
QLogger.deactivateCollectingLoggerForClass(QuartzScheduler.class);
try
{
QScheduleManager.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
try
{
QuartzScheduler.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
SchedulerTestUtils.afterEach();
}

View File

@ -62,7 +62,7 @@ class QuartzSchedulerTest extends BaseTest
@AfterEach
void afterEach()
{
QuartzTestUtils.afterEach();
SchedulerTestUtils.afterEach();
}

View File

@ -27,7 +27,6 @@ import java.util.Properties;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.quartz.QuartzSchedulerMetaData;
import com.kingsrook.qqq.backend.core.scheduler.QScheduleManager;
import org.quartz.SchedulerException;
@ -104,34 +103,4 @@ public class QuartzTestUtils
return QuartzScheduler.getInstance().queryQuartz();
}
/*******************************************************************************
**
*******************************************************************************/
public static void afterEach()
{
try
{
QScheduleManager.getInstance().stop();
QScheduleManager.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
try
{
QuartzScheduler.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
}
}

View File

@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
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.scheduler.QScheduleManager;
import com.kingsrook.qqq.backend.core.scheduler.SchedulerTestUtils;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzJobAndTriggerWrapper;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzTestUtils;
@ -92,7 +93,7 @@ class QuartzJobsProcessTest extends BaseTest
@AfterEach
void afterEach()
{
QuartzTestUtils.afterEach();
SchedulerTestUtils.afterEach();
}

View File

@ -53,7 +53,7 @@ class SimpleSchedulerTest extends BaseTest
@AfterEach
void afterEach()
{
QScheduleManager.getInstance().unInit();
SchedulerTestUtils.afterEach();
}

View File

@ -40,6 +40,14 @@ public class PersonQRecord extends QRecord
public PersonQRecord withFirstName(String firstName)
{
setValue("firstName", firstName);
return (this);
}
public PersonQRecord withBirthDate(LocalDate birthDate)
{
setValue("birthDate", birthDate);

View File

@ -25,7 +25,9 @@ package com.kingsrook.qqq.backend.core.utils;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
@ -79,4 +81,20 @@ class ObjectUtilsTest
assertEquals("else", ObjectUtils.tryAndRequireNonNullElse(() -> null, "else"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testIfCan()
{
Object nullObject = null;
assertTrue(ObjectUtils.ifCan(() -> true));
assertTrue(ObjectUtils.ifCan(() -> "a".equals("a")));
assertFalse(ObjectUtils.ifCan(() -> 1 == 2));
assertFalse(ObjectUtils.ifCan(() -> nullObject.equals("a")));
assertFalse(ObjectUtils.ifCan(() -> null));
}
}

View File

@ -138,6 +138,7 @@ public class TestUtils
public static final String APP_NAME_PEOPLE = "peopleApp";
public static final String APP_NAME_MISCELLANEOUS = "miscellaneous";
public static final String TABLE_NAME_TWO_KEYS = "twoKeys";
public static final String TABLE_NAME_PERSON = "person";
public static final String TABLE_NAME_SHAPE = "shape";
public static final String TABLE_NAME_SHAPE_CACHE = "shapeCache";
@ -196,6 +197,7 @@ public class TestUtils
qInstance.addBackend(defineMemoryBackend());
qInstance.addTable(defineTablePerson());
qInstance.addTable(defineTableTwoKeys());
qInstance.addTable(definePersonFileTable());
qInstance.addTable(definePersonMemoryTable());
qInstance.addTable(definePersonMemoryCacheTable());
@ -545,6 +547,24 @@ public class TestUtils
/*******************************************************************************
** Define the 'two key' table used in standard tests.
*******************************************************************************/
public static QTableMetaData defineTableTwoKeys()
{
return new QTableMetaData()
.withName(TABLE_NAME_TWO_KEYS)
.withLabel("Two Keys")
.withBackendName(MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withUniqueKey(new UniqueKey("key1", "key2"))
.withField(new QFieldMetaData("key1", QFieldType.INTEGER))
.withField(new QFieldMetaData("key2", QFieldType.INTEGER));
}
/*******************************************************************************
** Define the 'person' table used in standard tests.
*******************************************************************************/
@ -791,6 +811,26 @@ public class TestUtils
/*******************************************************************************
** Define a table with unique key where one is nullable
*******************************************************************************/
public static QTableMetaData defineTwoKeyTable()
{
return (new QTableMetaData()
.withName(TABLE_NAME_BASEPULL)
.withLabel("Basepull Test")
.withPrimaryKeyField("id")
.withBackendName(MEMORY_BACKEND_NAME)
.withFields(TestUtils.defineTablePerson().getFields()))
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date").withIsEditable(false))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date").withIsEditable(false))
.withField(new QFieldMetaData(BASEPULL_KEY_FIELD_NAME, QFieldType.STRING).withBackendName("process_name").withIsRequired(true))
.withField(new QFieldMetaData(BASEPULL_LAST_RUN_TIME_FIELD_NAME, QFieldType.DATE_TIME).withBackendName("last_run_time").withIsRequired(true));
}
/*******************************************************************************
** Define a basepullTable
*******************************************************************************/

View File

@ -23,6 +23,9 @@ package com.kingsrook.qqq.backend.core.utils.aggregates;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.Month;
import com.kingsrook.qqq.backend.core.BaseTest;
import org.assertj.core.data.Offset;
import org.junit.jupiter.api.Test;
@ -78,6 +81,12 @@ class AggregatesTest extends BaseTest
assertEquals(15, aggregates.getMax());
assertEquals(30, aggregates.getSum());
assertThat(aggregates.getAverage()).isCloseTo(new BigDecimal("10"), Offset.offset(BigDecimal.ZERO));
assertEquals(new BigDecimal("750"), aggregates.getProduct());
assertEquals(new BigDecimal("25.0000"), aggregates.getVariance());
assertEquals(new BigDecimal("5.0000"), aggregates.getStandardDeviation());
assertThat(aggregates.getVarP()).isCloseTo(new BigDecimal("16.6667"), Offset.offset(new BigDecimal(".0001")));
assertThat(aggregates.getStdDevP()).isCloseTo(new BigDecimal("4.0824"), Offset.offset(new BigDecimal(".0001")));
}
@ -89,6 +98,7 @@ class AggregatesTest extends BaseTest
void testBigDecimal()
{
BigDecimalAggregates aggregates = new BigDecimalAggregates();
aggregates.add(null);
assertEquals(0, aggregates.getCount());
assertNull(aggregates.getMin());
@ -114,13 +124,117 @@ class AggregatesTest extends BaseTest
BigDecimal bd148 = new BigDecimal("14.8");
aggregates.add(bd148);
aggregates.add(null);
assertEquals(3, aggregates.getCount());
assertEquals(bd51, aggregates.getMin());
assertEquals(bd148, aggregates.getMax());
assertEquals(new BigDecimal("30.0"), aggregates.getSum());
assertThat(aggregates.getAverage()).isCloseTo(new BigDecimal("10.0"), Offset.offset(BigDecimal.ZERO));
assertEquals(new BigDecimal("762.348"), aggregates.getProduct());
assertEquals(new BigDecimal("23.5300"), aggregates.getVariance());
assertEquals(new BigDecimal("4.8508"), aggregates.getStandardDeviation());
assertThat(aggregates.getVarP()).isCloseTo(new BigDecimal("15.6867"), Offset.offset(new BigDecimal(".0001")));
assertThat(aggregates.getStdDevP()).isCloseTo(new BigDecimal("3.9606"), Offset.offset(new BigDecimal(".0001")));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testInstant()
{
InstantAggregates aggregates = new InstantAggregates();
assertEquals(0, aggregates.getCount());
assertNull(aggregates.getMin());
assertNull(aggregates.getMax());
assertNull(aggregates.getSum());
assertNull(aggregates.getAverage());
Instant i1970 = Instant.parse("1970-01-01T00:00:00Z");
aggregates.add(i1970);
assertEquals(1, aggregates.getCount());
assertEquals(i1970, aggregates.getMin());
assertEquals(i1970, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(i1970, aggregates.getAverage());
Instant i1980 = Instant.parse("1980-01-01T00:00:00Z");
aggregates.add(i1980);
assertEquals(2, aggregates.getCount());
assertEquals(i1970, aggregates.getMin());
assertEquals(i1980, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(Instant.parse("1975-01-01T00:00:00Z"), aggregates.getAverage());
Instant i1990 = Instant.parse("1990-01-01T00:00:00Z");
aggregates.add(i1990);
assertEquals(3, aggregates.getCount());
assertEquals(i1970, aggregates.getMin());
assertEquals(i1990, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(Instant.parse("1980-01-01T08:00:00Z"), aggregates.getAverage()); // a leap day throws this off by 8 hours :)
/////////////////////////////////////////////////////////////////////
// assert we gracefully return null for these ops we don't support //
/////////////////////////////////////////////////////////////////////
assertNull(aggregates.getProduct());
assertNull(aggregates.getVariance());
assertNull(aggregates.getStandardDeviation());
assertNull(aggregates.getVarP());
assertNull(aggregates.getStdDevP());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testLocalDate()
{
LocalDateAggregates aggregates = new LocalDateAggregates();
assertEquals(0, aggregates.getCount());
assertNull(aggregates.getMin());
assertNull(aggregates.getMax());
assertNull(aggregates.getSum());
assertNull(aggregates.getAverage());
LocalDate ld1970 = LocalDate.of(1970, Month.JANUARY, 1);
aggregates.add(ld1970);
assertEquals(1, aggregates.getCount());
assertEquals(ld1970, aggregates.getMin());
assertEquals(ld1970, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(ld1970, aggregates.getAverage());
LocalDate ld1980 = LocalDate.of(1980, Month.JANUARY, 1);
aggregates.add(ld1980);
assertEquals(2, aggregates.getCount());
assertEquals(ld1970, aggregates.getMin());
assertEquals(ld1980, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(LocalDate.of(1975, Month.JANUARY, 1), aggregates.getAverage());
LocalDate ld1990 = LocalDate.of(1990, Month.JANUARY, 1);
aggregates.add(ld1990);
assertEquals(3, aggregates.getCount());
assertEquals(ld1970, aggregates.getMin());
assertEquals(ld1990, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(ld1980, aggregates.getAverage());
/////////////////////////////////////////////////////////////////////
// assert we gracefully return null for these ops we don't support //
/////////////////////////////////////////////////////////////////////
assertNull(aggregates.getProduct());
assertNull(aggregates.getVariance());
assertNull(aggregates.getStandardDeviation());
assertNull(aggregates.getVarP());
assertNull(aggregates.getStdDevP());
}
}