Merged dev into feature/rdbms-connection-pool

This commit is contained in:
2024-06-04 20:06:50 -05:00
489 changed files with 43583 additions and 3406 deletions

View File

@ -32,7 +32,9 @@ import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -77,6 +79,7 @@ import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput;
import com.kingsrook.qqq.backend.core.model.actions.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.QInputSource;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
@ -100,6 +103,7 @@ import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSo
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
@ -107,6 +111,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
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.frontend.QFrontendVariant;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
@ -279,6 +284,14 @@ public class QJavalinImplementation
try
{
////////////////////////////////////////////////////////////////////////////////
// clear the cache of classes in this class, so that new classes can be found //
////////////////////////////////////////////////////////////////////////////////
MetaDataProducerHelper.clearTopLevelClassCache();
/////////////////////////////////////////////////
// try to get a new instance from the supplier //
/////////////////////////////////////////////////
QInstance newQInstance = qInstanceHotSwapSupplier.get();
if(newQInstance == null)
{
@ -286,6 +299,9 @@ public class QJavalinImplementation
return;
}
///////////////////////////////////////////////////////////////////////////////////
// validate the instance, and only if it passes, then set it in our static field //
///////////////////////////////////////////////////////////////////////////////////
new QInstanceValidator().validate(newQInstance);
QJavalinImplementation.qInstance = newQInstance;
LOG.info("Swapped qInstance");
@ -364,8 +380,8 @@ public class QJavalinImplementation
post("/export", QJavalinImplementation::dataExportWithoutFilename);
get("/export/{filename}", QJavalinImplementation::dataExportWithFilename);
post("/export/{filename}", QJavalinImplementation::dataExportWithFilename);
get("/possibleValues/{fieldName}", QJavalinImplementation::possibleValues);
post("/possibleValues/{fieldName}", QJavalinImplementation::possibleValues);
get("/possibleValues/{fieldName}", QJavalinImplementation::possibleValuesForTableField);
post("/possibleValues/{fieldName}", QJavalinImplementation::possibleValuesForTableField);
// todo - add put and/or patch at this level (without a primaryKey) to do a bulk update based on primaryKeys in the records.
path("/{primaryKey}", () ->
@ -382,6 +398,9 @@ public class QJavalinImplementation
});
});
get("/possibleValues/{possibleValueSourceName}", QJavalinImplementation::possibleValuesStandalone);
post("/possibleValues/{possibleValueSourceName}", QJavalinImplementation::possibleValuesStandalone);
get("/widget/{name}", QJavalinImplementation::widget); // todo - can we just do a slow log here?
get("/serverInfo", QJavalinImplementation::serverInfo);
@ -427,7 +446,17 @@ public class QJavalinImplementation
QSession session = authenticationModule.createSession(qInstance, authContext);
context.cookie(SESSION_UUID_COOKIE_NAME, session.getUuid(), SESSION_COOKIE_AGE);
context.result(JsonUtils.toJson(MapBuilder.of("uuid", session.getUuid())));
Map<String, Serializable> resultMap = new HashMap<>();
resultMap.put("uuid", session.getUuid());
if(session.getValuesForFrontend() != null)
{
LinkedHashMap<String, Serializable> valuesForFrontend = new LinkedHashMap<>(session.getValuesForFrontend());
resultMap.put("values", valuesForFrontend);
}
context.result(JsonUtils.toJson(resultMap));
}
catch(Exception e)
{
@ -710,10 +739,15 @@ public class QJavalinImplementation
{
throw (new QUserFacingException("Error updating " + tableMetaData.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getErrors())));
}
if(CollectionUtils.nullSafeHasContents(outputRecord.getWarnings()))
{
throw (new QUserFacingException("Warning updating " + tableMetaData.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getWarnings())));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
// at one time, we threw upon warning - but //
// on insert we need to return the record (e.g., to get a generated id), so, make update do the same. //
////////////////////////////////////////////////////////////////////////////////////////////////////////
// if(CollectionUtils.nullSafeHasContents(outputRecord.getWarnings()))
// {
// throw (new QUserFacingException("Warning updating " + tableMetaData.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getWarnings())));
// }
QJavalinAccessLogger.logEndSuccess();
context.result(JsonUtils.toJson(updateOutput));
@ -779,9 +813,34 @@ public class QJavalinImplementation
{
String fieldName = formParam.getKey();
List<String> values = formParam.getValue();
if(CollectionUtils.nullSafeHasContents(values))
{
String value = values.get(0);
if("associations".equals(fieldName) && StringUtils.hasContent(value))
{
JSONObject associationsJSON = new JSONObject(value);
for(String key : associationsJSON.keySet())
{
JSONArray associatedRecordsJSON = associationsJSON.getJSONArray(key);
List<QRecord> associatedRecords = new ArrayList<>();
record.withAssociatedRecords(key, associatedRecords);
for(int i = 0; i < associatedRecordsJSON.length(); i++)
{
QRecord associatedRecord = new QRecord();
JSONObject recordJSON = associatedRecordsJSON.getJSONObject(i);
for(String k : recordJSON.keySet())
{
associatedRecord.withValue(k, ValueUtils.getValueAsString(recordJSON.get(k)));
}
associatedRecords.add(associatedRecord);
}
}
continue;
}
if(StringUtils.hasContent(value))
{
record.setValue(fieldName, value);
@ -793,7 +852,6 @@ public class QJavalinImplementation
}
else
{
// is this ever hit?
record.setValue(fieldName, null);
}
}
@ -881,10 +939,16 @@ public class QJavalinImplementation
{
throw (new QUserFacingException("Error inserting " + table.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getErrors())));
}
if(CollectionUtils.nullSafeHasContents(outputRecord.getWarnings()))
{
throw (new QUserFacingException("Warning inserting " + table.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getWarnings())));
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// at one time, we threw upon warning - but //
// our use-case is, the frontend, it wants to get the record, and show a success (with the generated id) //
// and then to also show a warning message - so - let it all be returned and handled on the frontend. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// if(CollectionUtils.nullSafeHasContents(outputRecord.getWarnings()))
// {
// throw (new QUserFacingException("Warning inserting " + table.getLabel() + ": " + joinErrorsWithCommasAndAnd(outputRecord.getWarnings())));
// }
QJavalinAccessLogger.logEndSuccess(logPair("primaryKey", () -> (outputRecord.getValue(table.getPrimaryKeyField()))));
context.result(JsonUtils.toJson(insertOutput));
@ -1196,7 +1260,9 @@ public class QJavalinImplementation
}
if(filter != null)
{
queryInput.setFilter(JsonUtils.toObject(filter, QQueryFilter.class));
QQueryFilter qQueryFilter = JsonUtils.toObject(filter, QQueryFilter.class);
queryInput.setFilter(qQueryFilter);
qQueryFilter.interpretValues(Collections.emptyMap());
}
Integer skip = QJavalinUtils.integerQueryParam(context, "skip");
@ -1460,10 +1526,11 @@ public class QJavalinImplementation
setupSession(context, exportInput);
exportInput.setTableName(tableName);
exportInput.setReportFormat(reportFormat);
String filename = optionalFilename.orElse(tableName + "." + reportFormat.toString().toLowerCase(Locale.ROOT));
exportInput.setFilename(filename);
exportInput.withReportDestination(new ReportDestination()
.withReportFormat(reportFormat)
.withFilename(filename));
Integer limit = QJavalinUtils.integerQueryParam(context, "limit");
exportInput.setLimit(limit);
@ -1494,7 +1561,7 @@ public class QJavalinImplementation
UnsafeFunction<PipedOutputStream, ExportAction, Exception> preAction = (PipedOutputStream pos) ->
{
exportInput.setReportOutputStream(pos);
exportInput.getReportDestination().setReportOutputStream(pos);
ExportAction exportAction = new ExportAction();
exportAction.preExecute(exportInput);
@ -1650,9 +1717,9 @@ public class QJavalinImplementation
/*******************************************************************************
**
** handler for a PVS that's associated with a field on a table.
*******************************************************************************/
private static void possibleValues(Context context)
private static void possibleValuesForTableField(Context context)
{
try
{
@ -1691,36 +1758,71 @@ public class QJavalinImplementation
/*******************************************************************************
**
** handler for a standalone (e.g., outside of a table or process) PVS.
*******************************************************************************/
private static void possibleValuesStandalone(Context context)
{
try
{
String possibleValueSourceName = context.pathParam("possibleValueSourceName");
QPossibleValueSource pvs = qInstance.getPossibleValueSource(possibleValueSourceName);
if(pvs == null)
{
throw (new QNotFoundException("Could not find possible value source " + possibleValueSourceName + " in this instance."));
}
finishPossibleValuesRequest(context, possibleValueSourceName, null);
}
catch(Exception e)
{
handleException(context, e);
}
}
/*******************************************************************************
** continuation for table or process PVS's,
*******************************************************************************/
static void finishPossibleValuesRequest(Context context, QFieldMetaData field) throws IOException, QException
{
QQueryFilter defaultQueryFilter = null;
if(field.getPossibleValueSourceFilter() != null)
{
Map<String, Serializable> values = new HashMap<>();
if(context.formParamMap().containsKey("values"))
{
List<String> valuesParamList = context.formParamMap().get("values");
if(CollectionUtils.nullSafeHasContents(valuesParamList))
{
String valuesParam = valuesParamList.get(0);
values = JsonUtils.toObject(valuesParam, Map.class);
}
}
defaultQueryFilter = field.getPossibleValueSourceFilter().clone();
defaultQueryFilter.interpretValues(values);
}
finishPossibleValuesRequest(context, field.getPossibleValueSourceName(), defaultQueryFilter);
}
/*******************************************************************************
**
*******************************************************************************/
static void finishPossibleValuesRequest(Context context, String possibleValueSourceName, QQueryFilter defaultFilter) throws IOException, QException
{
String searchTerm = context.queryParam("searchTerm");
String ids = context.queryParam("ids");
Map<String, Serializable> values = new HashMap<>();
if(context.formParamMap().containsKey("values"))
{
List<String> valuesParamList = context.formParamMap().get("values");
if(CollectionUtils.nullSafeHasContents(valuesParamList))
{
String valuesParam = valuesParamList.get(0);
values = JsonUtils.toObject(valuesParam, Map.class);
}
}
SearchPossibleValueSourceInput input = new SearchPossibleValueSourceInput();
setupSession(context, input);
input.setPossibleValueSourceName(field.getPossibleValueSourceName());
input.setPossibleValueSourceName(possibleValueSourceName);
input.setSearchTerm(searchTerm);
if(field.getPossibleValueSourceFilter() != null)
{
QQueryFilter filter = field.getPossibleValueSourceFilter().clone();
filter.interpretValues(values);
input.setDefaultQueryFilter(filter);
}
if(StringUtils.hasContent(ids))
{
List<Serializable> idList = new ArrayList<>(Arrays.asList(ids.split(",")));
@ -1732,6 +1834,7 @@ public class QJavalinImplementation
Map<String, Object> result = new HashMap<>();
result.put("options", output.getResults());
context.result(JsonUtils.toJson(result));
}
@ -1876,4 +1979,13 @@ public class QJavalinImplementation
MILLIS_BETWEEN_HOT_SWAPS = millisBetweenHotSwaps;
}
/*******************************************************************************
**
*******************************************************************************/
public static long getStartTimeMillis()
{
return (startTime);
}
}

View File

@ -45,10 +45,12 @@ import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
import com.kingsrook.qqq.backend.core.actions.async.JobGoingAsyncException;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.actions.processes.CancelProcessAction;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
@ -60,15 +62,18 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
import com.kingsrook.qqq.backend.core.model.actions.processes.QUploadedFile;
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.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.tables.insert.InsertInput;
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.actions.tables.storage.StorageInput;
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.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@ -126,6 +131,7 @@ public class QJavalinProcessHandler
post("/step/{step}", QJavalinProcessHandler::processStep);
get("/status/{jobUUID}", QJavalinProcessHandler::processStatus);
get("/records", QJavalinProcessHandler::processRecords);
get("/cancel", QJavalinProcessHandler::processCancel);
});
get("/possibleValues/{fieldName}", QJavalinProcessHandler::possibleValues);
@ -203,10 +209,12 @@ public class QJavalinProcessHandler
QJavalinImplementation.setupSession(context, reportInput);
PermissionsHelper.checkReportPermissionThrowing(reportInput, reportName);
reportInput.setReportFormat(reportFormat);
reportInput.setReportName(reportName);
reportInput.setInputValues(null); // todo!
reportInput.setFilename(filename);
reportInput.setReportDestination(new ReportDestination()
.withReportFormat(reportFormat)
.withFilename(filename));
//////////////////////////////////////////////////////////////
// process the report's input fields, from the query string //
@ -239,7 +247,7 @@ public class QJavalinProcessHandler
UnsafeFunction<PipedOutputStream, GenerateReportAction, Exception> preAction = (PipedOutputStream pos) ->
{
reportInput.setReportOutputStream(pos);
reportInput.getReportDestination().setReportOutputStream(pos);
GenerateReportAction reportAction = new GenerateReportAction();
// any pre-action?? export uses this for "too many rows" checks...
@ -282,12 +290,24 @@ public class QJavalinProcessHandler
// todo context.contentType(reportFormat.getMimeType());
context.header("Content-Disposition", "filename=" + context.pathParam("file"));
String filePath = context.queryParam("filePath");
if(filePath == null)
String filePath = context.queryParam("filePath");
String storageTableName = context.queryParam("storageTableName");
String reference = context.queryParam("storageReference");
if(filePath != null)
{
throw (new QBadRequestException("A filePath was not provided."));
context.result(new FileInputStream(filePath));
}
context.result(new FileInputStream(filePath));
else if(storageTableName != null && reference != null)
{
InputStream inputStream = new StorageAction().getInputStream(new StorageInput(storageTableName).withReference(reference));
context.result(inputStream);
}
else
{
throw (new QBadRequestException("Missing query parameters to identify file to download"));
}
}
catch(Exception e)
{
@ -431,6 +451,12 @@ public class QJavalinProcessHandler
}
resultForCaller.put("values", runProcessOutput.getValues());
runProcessOutput.getProcessState().getNextStepName().ifPresent(nextStep -> resultForCaller.put("nextStep", nextStep));
List<QFrontendStepMetaData> updatedFrontendStepList = runProcessOutput.getUpdatedFrontendStepList();
if(updatedFrontendStepList != null)
{
resultForCaller.put("updatedFrontendStepList", updatedFrontendStepList);
}
}
@ -744,6 +770,32 @@ public class QJavalinProcessHandler
/*******************************************************************************
**
*******************************************************************************/
private static void processCancel(Context context)
{
try
{
RunProcessInput runProcessInput = new RunProcessInput();
QJavalinImplementation.setupSession(context, runProcessInput);
runProcessInput.setProcessName(context.pathParam("processName"));
runProcessInput.setProcessUUID(context.pathParam("processUUID"));
new CancelProcessAction().execute(runProcessInput);
Map<String, Object> resultForCaller = new HashMap<>();
context.result(JsonUtils.toJson(resultForCaller));
}
catch(Exception e)
{
QJavalinImplementation.handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -39,6 +39,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.modules.backend.implementations.mock.MockBackendModule;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
import kong.unirest.HttpResponse;
@ -514,6 +515,42 @@ class QJavalinImplementationTest extends QJavalinTestBase
/*******************************************************************************
** test an insert that returns a warning
**
*******************************************************************************/
@Test
public void test_dataInsertWithWarning()
{
Map<String, Serializable> body = new HashMap<>();
body.put("firstName", "Warning");
body.put("lastName", "Kelkhoff");
body.put("email", "warning@kelkhoff.com");
HttpResponse<String> response = Unirest.post(BASE_URL + "/data/person")
.header("Content-Type", "application/json")
.body(body)
.asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertTrue(jsonObject.has("records"));
JSONArray records = jsonObject.getJSONArray("records");
assertEquals(1, records.length());
JSONObject record0 = records.getJSONObject(0);
assertTrue(record0.has("values"));
JSONObject values0 = record0.getJSONObject("values");
assertTrue(values0.has("id"));
assertEquals(7, values0.getInt("id"));
assertTrue(record0.has("warnings"));
JSONArray warnings = record0.getJSONArray("warnings");
assertEquals(1, warnings.length());
assertTrue(warnings.getJSONObject(0).has("message"));
}
/*******************************************************************************
** test an insert - posting a multipart form.
**
@ -594,6 +631,52 @@ class QJavalinImplementationTest extends QJavalinTestBase
/*******************************************************************************
** test an update - with a warning returned
**
*******************************************************************************/
@Test
public void test_dataUpdateWithWarning()
{
Map<String, Serializable> body = new HashMap<>();
body.put("firstName", "Warning");
body.put("birthDate", "");
HttpResponse<String> response = Unirest.patch(BASE_URL + "/data/person/4")
.header("Content-Type", "application/json")
.body(body)
.asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertTrue(jsonObject.has("records"));
JSONArray records = jsonObject.getJSONArray("records");
assertEquals(1, records.length());
JSONObject record0 = records.getJSONObject(0);
assertTrue(record0.has("values"));
assertEquals("person", record0.getString("tableName"));
JSONObject values0 = record0.getJSONObject("values");
assertEquals(4, values0.getInt("id"));
assertEquals("Warning", values0.getString("firstName"));
assertTrue(record0.has("warnings"));
JSONArray warnings = record0.getJSONArray("warnings");
assertEquals(1, warnings.length());
assertTrue(warnings.getJSONObject(0).has("message"));
///////////////////////////////////////////////////////////////////
// re-GET the record, and validate that birthDate was nulled out //
///////////////////////////////////////////////////////////////////
response = Unirest.get(BASE_URL + "/data/person/4").asString();
assertEquals(200, response.getStatus());
jsonObject = JsonUtils.toJSONObject(response.getBody());
assertTrue(jsonObject.has("values"));
JSONObject values = jsonObject.getJSONObject("values");
assertFalse(values.has("birthDate"));
}
/*******************************************************************************
** test an update - posting the data as a multipart form
**
@ -761,6 +844,24 @@ class QJavalinImplementationTest extends QJavalinTestBase
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPossibleValueWithoutTableOrProcess()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/possibleValues/person").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);
assertNotNull(jsonObject.getJSONArray("options"));
assertEquals(6, jsonObject.getJSONArray("options").length());
assertEquals(1, jsonObject.getJSONArray("options").getJSONObject(0).getInt("id"));
assertEquals("Darin Kelkhoff (1)", jsonObject.getJSONArray("options").getJSONObject(0).getString("label"));
}
/*******************************************************************************
**
*******************************************************************************/
@ -882,7 +983,7 @@ class QJavalinImplementationTest extends QJavalinTestBase
Function<String, QInstance> makeNewInstanceWithBackendName = (backendName) ->
{
QInstance newInstance = new QInstance();
newInstance.addBackend(new QBackendMetaData().withName(backendName).withBackendType("mock"));
newInstance.addBackend(new QBackendMetaData().withName(backendName).withBackendType(MockBackendModule.class));
if(!"invalid".equals(backendName))
{

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.javalin;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.List;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
@ -575,15 +576,15 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test calling download file with missing filePath
** test calling download file without needed query-string params
**
*******************************************************************************/
@Test
public void test_downloadFileMissingFilePath()
public void test_downloadFileMissingQueryStringParams()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/download/myTestFile.txt").asString();
assertEquals(400, response.getStatus());
assertTrue(response.getBody().contains("A filePath was not provided"));
assertTrue(response.getBody().contains("Missing query parameters to identify file"));
}
@ -604,4 +605,45 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
assertEquals(1, jsonObject.getJSONArray("options").getJSONObject(0).getInt("id"));
assertEquals("Darin Kelkhoff (1)", jsonObject.getJSONArray("options").getJSONObject(0).getString("label"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void test_processCancel()
{
/////////////////////////
// 400s for bad inputs //
/////////////////////////
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/noSuchProcess/" + UUID.randomUUID() + "/cancel").asString();
assertEquals(400, response.getStatus());
assertThat(response.getBody()).contains("Process [noSuchProcess] is not defined in this instance");
}
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_SLEEP + "/" + UUID.randomUUID() + "/cancel").asString();
assertEquals(400, response.getStatus());
assertThat(response.getBody()).matches(".*State for process UUID.*not found.*");
}
///////////////////////////////////
// start a process, get its uuid //
///////////////////////////////////
String processBasePath = BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SLEEP_INTERACTIVE;
HttpResponse<String> response = Unirest.post(processBasePath + "/init?" + TestUtils.SleeperStep.FIELD_SLEEP_MILLIS + "=" + MORE_THAN_TIMEOUT)
.header("Content-Type", "application/json").asString();
JSONObject jsonObject = assertProcessStepCompleteResponse(response);
String processUUID = jsonObject.getString("processUUID");
assertNotNull(processUUID, "Process UUID should not be null.");
/////////////////////////////////////////
// now cancel that, and expect success //
/////////////////////////////////////////
response = Unirest.get(BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SLEEP_INTERACTIVE + "/" + processUUID + "/cancel").asString();
assertEquals(200, response.getStatus());
}
}

View File

@ -25,6 +25,10 @@ package com.kingsrook.qqq.backend.javalin;
import java.io.InputStream;
import java.sql.Connection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@ -35,6 +39,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
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.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
@ -68,6 +73,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.savedviews.SavedViewsMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
@ -225,7 +232,7 @@ public class TestUtils
public static QBackendMetaData defineMemoryBackend()
{
return new QBackendMetaData()
.withBackendType("memory")
.withBackendType(MemoryBackendModule.class)
.withName(BACKEND_NAME_MEMORY);
}
@ -260,6 +267,9 @@ public class TestUtils
.withScriptTypeId(1)
.withScriptTester(new QCodeReference(TestScriptAction.class)));
qTableMetaData.withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(PersonTableCustomizer.class));
qTableMetaData.withCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(PersonTableCustomizer.class));
qTableMetaData.getField("photo")
.withIsHeavy(true)
.withFieldAdornment(new FieldAdornment(AdornmentType.FILE_DOWNLOAD)
@ -271,6 +281,52 @@ public class TestUtils
/*******************************************************************************
**
*******************************************************************************/
public static class PersonTableCustomizer implements TableCustomizerInterface
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
{
return warnPostInsertOrUpdate(records);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
return warnPostInsertOrUpdate(records);
}
/*******************************************************************************
**
*******************************************************************************/
private List<QRecord> warnPostInsertOrUpdate(List<QRecord> records)
{
for(QRecord record : records)
{
if(Objects.requireNonNullElse(record.getValueString("firstName"), "").toLowerCase().contains("warn"))
{
record.addWarning(new QWarningMessage("Warning in firstName."));
}
}
return records;
}
}
/*******************************************************************************
**
*******************************************************************************/