mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
QQQ-21 update to take recordIds or filter for initing process; refactor processes into their own javalin class & test
This commit is contained in:
2
pom.xml
2
pom.xml
@ -53,7 +53,7 @@
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-backend-core</artifactId>
|
||||
<version>0.1.0-20220708.195335-5</version>
|
||||
<version>0.1.0-20220708.203555-6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
|
@ -29,24 +29,15 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.kingsrook.qqq.backend.core.actions.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.MetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.ProcessMetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager;
|
||||
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.adapters.QInstanceAdapter;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
@ -64,9 +55,6 @@ import com.kingsrook.qqq.backend.core.model.actions.metadata.process.ProcessMeta
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.process.ProcessMetaDataResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
|
||||
@ -75,15 +63,11 @@ import com.kingsrook.qqq.backend.core.model.actions.query.QueryResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.QAuthenticationModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.interfaces.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.state.StateType;
|
||||
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
@ -114,12 +98,11 @@ public class QJavalinImplementation
|
||||
|
||||
private static final int SESSION_COOKIE_AGE = 60 * 60 * 24;
|
||||
|
||||
private static QInstance qInstance;
|
||||
protected static QInstance qInstance;
|
||||
|
||||
private static int DEFAULT_PORT = 8001;
|
||||
|
||||
private static int ASYNC_STEP_TIMEOUT_MILLIS = 3_000;
|
||||
|
||||
private static Javalin service;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -165,11 +148,20 @@ public class QJavalinImplementation
|
||||
{
|
||||
// todo port from arg
|
||||
// todo base path from arg?
|
||||
Javalin service = Javalin.create().start(port);
|
||||
service = Javalin.create().start(port);
|
||||
service.routes(getRoutes());
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void stopJavalinServer()
|
||||
{
|
||||
service.stop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
@ -181,16 +173,6 @@ public class QJavalinImplementation
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setAsyncStepTimeoutMillis(int asyncStepTimeoutMillis)
|
||||
{
|
||||
QJavalinImplementation.ASYNC_STEP_TIMEOUT_MILLIS = asyncStepTimeoutMillis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -230,20 +212,7 @@ public class QJavalinImplementation
|
||||
});
|
||||
});
|
||||
});
|
||||
path("/processes", () ->
|
||||
{
|
||||
path("/:processName", () ->
|
||||
{
|
||||
get("/init", QJavalinImplementation::processInit);
|
||||
post("/init", QJavalinImplementation::processInit);
|
||||
|
||||
path("/:processUUID", () ->
|
||||
{
|
||||
post("/step/:step", QJavalinImplementation::processStep);
|
||||
get("/status/:jobUUID", QJavalinImplementation::processStatus);
|
||||
});
|
||||
});
|
||||
});
|
||||
path("", QJavalinProcessHandler.getRoutes());
|
||||
});
|
||||
}
|
||||
|
||||
@ -252,7 +221,7 @@ public class QJavalinImplementation
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void setupSession(Context context, AbstractQRequest request) throws QModuleDispatchException
|
||||
static void setupSession(Context context, AbstractQRequest request) throws QModuleDispatchException
|
||||
{
|
||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(request.getAuthenticationMetaData());
|
||||
@ -465,6 +434,7 @@ public class QJavalinImplementation
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Filter parameter is a serialized QQueryFilter object, that is to say:
|
||||
@ -579,7 +549,7 @@ public class QJavalinImplementation
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void handleException(Context context, Exception e)
|
||||
public static void handleException(Context context, Exception e)
|
||||
{
|
||||
QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(e, QUserFacingException.class);
|
||||
if(userFacingException != null)
|
||||
@ -611,7 +581,7 @@ public class QJavalinImplementation
|
||||
** Returns null if no param (or empty value).
|
||||
** Throws QValueException for malformed numbers.
|
||||
*******************************************************************************/
|
||||
private static Integer integerQueryParam(Context context, String name) throws QValueException
|
||||
public static Integer integerQueryParam(Context context, String name) throws QValueException
|
||||
{
|
||||
String value = context.queryParam(name);
|
||||
if(StringUtils.hasContent(value))
|
||||
@ -639,223 +609,4 @@ public class QJavalinImplementation
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Init a process (named in path param :process)
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processInit(Context context) throws QException
|
||||
{
|
||||
doProcessInitOrStep(context, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void doProcessInitOrStep(Context context, String processUUID, String startAfterStep) throws QModuleDispatchException
|
||||
{
|
||||
if(processUUID == null)
|
||||
{
|
||||
processUUID = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
RunProcessRequest runProcessRequest = new RunProcessRequest(qInstance);
|
||||
setupSession(context, runProcessRequest);
|
||||
runProcessRequest.setProcessName(context.pathParam("processName"));
|
||||
runProcessRequest.setCallback(new QJavalinProcessCallback());
|
||||
runProcessRequest.setBackendOnly(true);
|
||||
runProcessRequest.setProcessUUID(processUUID);
|
||||
runProcessRequest.setStartAfterStep(startAfterStep);
|
||||
populateRunProcessRequestWithValuesFromContext(context, runProcessRequest);
|
||||
|
||||
LOG.info(startAfterStep == null ? "Initiating process [" + runProcessRequest.getProcessName() + "] [" + processUUID + "]"
|
||||
: "Resuming process [" + runProcessRequest.getProcessName() + "] [" + processUUID + "] after step [" + startAfterStep + "]");
|
||||
|
||||
Map<String, Object> resultForCaller = new HashMap<>();
|
||||
resultForCaller.put("processUUID", processUUID);
|
||||
|
||||
try
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// run the process as an async action //
|
||||
////////////////////////////////////////
|
||||
Integer timeout = getTimeoutMillis(context);
|
||||
RunProcessResult runProcessResult = new AsyncJobManager().startJob(timeout, TimeUnit.MILLISECONDS, (callback) ->
|
||||
{
|
||||
runProcessRequest.setAsyncJobCallback(callback);
|
||||
return (new RunProcessAction().execute(runProcessRequest));
|
||||
});
|
||||
|
||||
LOG.info("Process result error? " + runProcessResult.getException());
|
||||
for(QFieldMetaData outputField : qInstance.getProcess(runProcessRequest.getProcessName()).getOutputFields())
|
||||
{
|
||||
LOG.info("Process result output value: " + outputField.getName() + ": " + runProcessResult.getValues().get(outputField.getName()));
|
||||
}
|
||||
serializeRunProcessResultForCaller(resultForCaller, runProcessResult);
|
||||
}
|
||||
catch(JobGoingAsyncException jgae)
|
||||
{
|
||||
resultForCaller.put("jobUUID", jgae.getJobUUID());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// our other actions in here would do: handleException(context, e); //
|
||||
// which would return a 500 to the client. //
|
||||
// but - other process-step actions, they always return a 200, just with an //
|
||||
// optional error message - so - keep all of the processes consistent. //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
serializeRunProcessExceptionForCaller(resultForCaller, e);
|
||||
}
|
||||
|
||||
context.result(JsonUtils.toJson(resultForCaller));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Integer getTimeoutMillis(Context context)
|
||||
{
|
||||
Integer timeout = integerQueryParam(context, "_qStepTimeoutMillis");
|
||||
if(timeout == null)
|
||||
{
|
||||
timeout = ASYNC_STEP_TIMEOUT_MILLIS;
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Whether a step finished synchronously or asynchronously, return its data
|
||||
** to the caller the same way.
|
||||
*******************************************************************************/
|
||||
private static void serializeRunProcessResultForCaller(Map<String, Object> resultForCaller, RunProcessResult runProcessResult)
|
||||
{
|
||||
if(runProcessResult.getException().isPresent())
|
||||
{
|
||||
////////////////////////////////////////////////////////////////
|
||||
// per code coverage, this path may never actually get hit... //
|
||||
////////////////////////////////////////////////////////////////
|
||||
serializeRunProcessExceptionForCaller(resultForCaller, runProcessResult.getException().get());
|
||||
}
|
||||
resultForCaller.put("values", runProcessResult.getValues());
|
||||
runProcessResult.getProcessState().getNextStepName().ifPresent(lastStep -> resultForCaller.put("nextStep", lastStep));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void serializeRunProcessExceptionForCaller(Map<String, Object> resultForCaller, Exception exception)
|
||||
{
|
||||
QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(exception, QUserFacingException.class);
|
||||
|
||||
if(userFacingException != null)
|
||||
{
|
||||
LOG.info("User-facing exception in process", userFacingException);
|
||||
resultForCaller.put("error", userFacingException.getMessage()); // todo - put this somewhere else (make error an object w/ user-facing and/or other error?)
|
||||
}
|
||||
else
|
||||
{
|
||||
Throwable rootException = ExceptionUtils.getRootException(exception);
|
||||
LOG.warn("Uncaught Exception in process", exception);
|
||||
resultForCaller.put("error", "Original error message: " + rootException.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** take values from query-string params, and put them into the run process request
|
||||
** todo - better from POST body, or with a "field-" type of prefix??
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void populateRunProcessRequestWithValuesFromContext(Context context, RunProcessRequest runProcessRequest)
|
||||
{
|
||||
for(Map.Entry<String, List<String>> queryParam : context.queryParamMap().entrySet())
|
||||
{
|
||||
String fieldName = queryParam.getKey();
|
||||
List<String> values = queryParam.getValue();
|
||||
if(CollectionUtils.nullSafeHasContents(values))
|
||||
{
|
||||
runProcessRequest.addValue(fieldName, values.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Run a step in a process (named in path param :processName)
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processStep(Context context) throws QModuleDispatchException
|
||||
{
|
||||
String processUUID = context.pathParam("processUUID");
|
||||
String lastStep = context.pathParam("step");
|
||||
doProcessInitOrStep(context, processUUID, lastStep);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get status for a currently running process (step)
|
||||
*******************************************************************************/
|
||||
private static void processStatus(Context context)
|
||||
{
|
||||
String processUUID = context.pathParam("processUUID");
|
||||
String jobUUID = context.pathParam("jobUUID");
|
||||
|
||||
LOG.info("Request for status of job " + jobUUID);
|
||||
Optional<AsyncJobStatus> optionalJobStatus = new AsyncJobManager().getJobStatus(jobUUID);
|
||||
if(optionalJobStatus.isEmpty())
|
||||
{
|
||||
handleException(context, new RuntimeException("Could not find status of process step job"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Map<String, Object> resultForCaller = new HashMap<>();
|
||||
AsyncJobStatus jobStatus = optionalJobStatus.get();
|
||||
|
||||
resultForCaller.put("jobStatus", jobStatus);
|
||||
LOG.info("Job status is " + jobStatus.getState() + " for " + jobUUID);
|
||||
|
||||
if(jobStatus.getState().equals(AsyncJobState.COMPLETE))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the job is complete, get the process result from state provider, and return it //
|
||||
// this output should look like it did if the job finished synchronously!! //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
Optional<ProcessState> processState = RunProcessAction.getStateProvider().get(ProcessState.class, new UUIDAndTypeStateKey(UUID.fromString(processUUID), StateType.PROCESS_STATUS));
|
||||
if(processState.isPresent())
|
||||
{
|
||||
RunProcessResult runProcessResult = new RunProcessResult(processState.get());
|
||||
serializeRunProcessResultForCaller(resultForCaller, runProcessResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
handleException(context, new RuntimeException("Could not find process results"));
|
||||
}
|
||||
}
|
||||
else if(jobStatus.getState().equals(AsyncJobState.ERROR))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the job had an error (e.g., a process step threw), "nicely" serialize its exception for the caller //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(jobStatus.getCaughtException() != null)
|
||||
{
|
||||
serializeRunProcessExceptionForCaller(resultForCaller, jobStatus.getCaughtException());
|
||||
}
|
||||
}
|
||||
|
||||
context.result(JsonUtils.toJson(resultForCaller));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,409 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.javalin;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.kingsrook.qqq.backend.core.actions.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager;
|
||||
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.callbacks.QProcessCallback;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.state.StateType;
|
||||
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import io.javalin.apibuilder.EndpointGroup;
|
||||
import io.javalin.http.Context;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import static io.javalin.apibuilder.ApiBuilder.get;
|
||||
import static io.javalin.apibuilder.ApiBuilder.path;
|
||||
import static io.javalin.apibuilder.ApiBuilder.post;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QJavalinProcessHandler
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QJavalinProcessHandler.class);
|
||||
|
||||
private static int ASYNC_STEP_TIMEOUT_MILLIS = 3_000;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the routes
|
||||
*******************************************************************************/
|
||||
public static EndpointGroup getRoutes()
|
||||
{
|
||||
return (() ->
|
||||
{
|
||||
path("/processes", () ->
|
||||
{
|
||||
path("/:processName", () ->
|
||||
{
|
||||
get("/init", QJavalinProcessHandler::processInit);
|
||||
post("/init", QJavalinProcessHandler::processInit);
|
||||
|
||||
path("/:processUUID", () ->
|
||||
{
|
||||
post("/step/:step", QJavalinProcessHandler::processStep);
|
||||
get("/status/:jobUUID", QJavalinProcessHandler::processStatus);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Init a process (named in path param :process)
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void processInit(Context context) throws QException
|
||||
{
|
||||
doProcessInitOrStep(context, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void doProcessInitOrStep(Context context, String processUUID, String startAfterStep)
|
||||
{
|
||||
Map<String, Object> resultForCaller = new HashMap<>();
|
||||
|
||||
try
|
||||
{
|
||||
if(processUUID == null)
|
||||
{
|
||||
processUUID = UUID.randomUUID().toString();
|
||||
}
|
||||
resultForCaller.put("processUUID", processUUID);
|
||||
|
||||
String processName = context.pathParam("processName");
|
||||
LOG.info(startAfterStep == null ? "Initiating process [" + processName + "] [" + processUUID + "]"
|
||||
: "Resuming process [" + processName + "] [" + processUUID + "] after step [" + startAfterStep + "]");
|
||||
|
||||
RunProcessRequest runProcessRequest = new RunProcessRequest(QJavalinImplementation.qInstance);
|
||||
QJavalinImplementation.setupSession(context, runProcessRequest);
|
||||
runProcessRequest.setProcessName(processName);
|
||||
runProcessRequest.setFrontendStepBehavior(RunProcessRequest.FrontendStepBehavior.BREAK);
|
||||
runProcessRequest.setProcessUUID(processUUID);
|
||||
runProcessRequest.setStartAfterStep(startAfterStep);
|
||||
populateRunProcessRequestWithValuesFromContext(context, runProcessRequest);
|
||||
|
||||
try
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// run the process as an async action //
|
||||
////////////////////////////////////////
|
||||
Integer timeout = getTimeoutMillis(context);
|
||||
RunProcessResult runProcessResult = new AsyncJobManager().startJob(timeout, TimeUnit.MILLISECONDS, (callback) ->
|
||||
{
|
||||
runProcessRequest.setAsyncJobCallback(callback);
|
||||
return (new RunProcessAction().execute(runProcessRequest));
|
||||
});
|
||||
|
||||
LOG.info("Process result error? " + runProcessResult.getException());
|
||||
for(QFieldMetaData outputField : QJavalinImplementation.qInstance.getProcess(runProcessRequest.getProcessName()).getOutputFields())
|
||||
{
|
||||
LOG.info("Process result output value: " + outputField.getName() + ": " + runProcessResult.getValues().get(outputField.getName()));
|
||||
}
|
||||
serializeRunProcessResultForCaller(resultForCaller, runProcessResult);
|
||||
}
|
||||
catch(JobGoingAsyncException jgae)
|
||||
{
|
||||
resultForCaller.put("jobUUID", jgae.getJobUUID());
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// our other actions in here would do: handleException(context, e); //
|
||||
// which would return a 500 to the client. //
|
||||
// but - other process-step actions, they always return a 200, just with an //
|
||||
// optional error message - so - keep all of the processes consistent. //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
serializeRunProcessExceptionForCaller(resultForCaller, e);
|
||||
}
|
||||
|
||||
context.result(JsonUtils.toJson(resultForCaller));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Whether a step finished synchronously or asynchronously, return its data
|
||||
** to the caller the same way.
|
||||
*******************************************************************************/
|
||||
private static void serializeRunProcessResultForCaller(Map<String, Object> resultForCaller, RunProcessResult runProcessResult)
|
||||
{
|
||||
if(runProcessResult.getException().isPresent())
|
||||
{
|
||||
////////////////////////////////////////////////////////////////
|
||||
// per code coverage, this path may never actually get hit... //
|
||||
////////////////////////////////////////////////////////////////
|
||||
serializeRunProcessExceptionForCaller(resultForCaller, runProcessResult.getException().get());
|
||||
}
|
||||
resultForCaller.put("values", runProcessResult.getValues());
|
||||
runProcessResult.getProcessState().getNextStepName().ifPresent(lastStep -> resultForCaller.put("nextStep", lastStep));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void serializeRunProcessExceptionForCaller(Map<String, Object> resultForCaller, Exception exception)
|
||||
{
|
||||
QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(exception, QUserFacingException.class);
|
||||
|
||||
if(userFacingException != null)
|
||||
{
|
||||
LOG.info("User-facing exception in process", userFacingException);
|
||||
resultForCaller.put("error", userFacingException.getMessage()); // todo - put this somewhere else (make error an object w/ user-facing and/or other error?)
|
||||
}
|
||||
else
|
||||
{
|
||||
Throwable rootException = ExceptionUtils.getRootException(exception);
|
||||
LOG.warn("Uncaught Exception in process", exception);
|
||||
resultForCaller.put("error", "Original error message: " + rootException.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** take values from query-string params, and put them into the run process request
|
||||
** todo - better from POST body, or with a "field-" type of prefix??
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void populateRunProcessRequestWithValuesFromContext(Context context, RunProcessRequest runProcessRequest) throws IOException
|
||||
{
|
||||
for(Map.Entry<String, List<String>> queryParam : context.queryParamMap().entrySet())
|
||||
{
|
||||
String fieldName = queryParam.getKey();
|
||||
List<String> values = queryParam.getValue();
|
||||
if(CollectionUtils.nullSafeHasContents(values))
|
||||
{
|
||||
runProcessRequest.addValue(fieldName, values.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
QQueryFilter initialRecordsFilter = buildProcessInitRecordsFilter(context, runProcessRequest);
|
||||
if(initialRecordsFilter != null)
|
||||
{
|
||||
runProcessRequest.setCallback(new QProcessCallback()
|
||||
{
|
||||
@Override
|
||||
public QQueryFilter getQueryFilter()
|
||||
{
|
||||
return (initialRecordsFilter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, Serializable> getFieldValues(List<QFieldMetaData> fields)
|
||||
{
|
||||
return (Collections.emptyMap());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QQueryFilter buildProcessInitRecordsFilter(Context context, RunProcessRequest runProcessRequest) throws IOException
|
||||
{
|
||||
QInstance instance = runProcessRequest.getInstance();
|
||||
QProcessMetaData process = instance.getProcess(runProcessRequest.getProcessName());
|
||||
QTableMetaData table = instance.getTable(process.getTableName());
|
||||
|
||||
if(table == null)
|
||||
{
|
||||
LOG.info("No table found in process - so not building an init records filter.");
|
||||
return (null);
|
||||
}
|
||||
String primaryKeyField = table.getPrimaryKeyField();
|
||||
|
||||
String recordsParam = context.queryParam("recordsParam");
|
||||
if(StringUtils.hasContent(recordsParam))
|
||||
{
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
String paramValue = context.queryParam(recordsParam);
|
||||
if(!StringUtils.hasContent(paramValue))
|
||||
{
|
||||
throw (new IllegalArgumentException("Missing value in query parameter: " + recordsParam + " (which was specified as the recordsParam)"));
|
||||
}
|
||||
|
||||
switch(recordsParam)
|
||||
{
|
||||
case "recordIds":
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
Serializable[] idStrings = context.queryParam(recordsParam).split(",");
|
||||
return (new QQueryFilter().withCriteria(new QFilterCriteria()
|
||||
.withFieldName(primaryKeyField)
|
||||
.withOperator(QCriteriaOperator.IN)
|
||||
.withValues(Arrays.stream(idStrings).toList())));
|
||||
case "filterJSON":
|
||||
return (JsonUtils.toObject(context.queryParam(recordsParam), QQueryFilter.class));
|
||||
case "filterId":
|
||||
// return (JsonUtils.toObject(context.queryParam(recordsParam), QQueryFilter.class));
|
||||
throw (new NotImplementedException("Saved filters are not yet implemented."));
|
||||
default:
|
||||
throw (new IllegalArgumentException("Unrecognized value [" + recordsParam + "] for query parameter: recordsParam"));
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Run a step in a process (named in path param :processName)
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void processStep(Context context) throws QModuleDispatchException
|
||||
{
|
||||
String processUUID = context.pathParam("processUUID");
|
||||
String lastStep = context.pathParam("step");
|
||||
doProcessInitOrStep(context, processUUID, lastStep);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get status for a currently running process (step)
|
||||
*******************************************************************************/
|
||||
public static void processStatus(Context context)
|
||||
{
|
||||
String processUUID = context.pathParam("processUUID");
|
||||
String jobUUID = context.pathParam("jobUUID");
|
||||
|
||||
LOG.info("Request for status of job " + jobUUID);
|
||||
Optional<AsyncJobStatus> optionalJobStatus = new AsyncJobManager().getJobStatus(jobUUID);
|
||||
if(optionalJobStatus.isEmpty())
|
||||
{
|
||||
QJavalinImplementation.handleException(context, new RuntimeException("Could not find status of process step job"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Map<String, Object> resultForCaller = new HashMap<>();
|
||||
AsyncJobStatus jobStatus = optionalJobStatus.get();
|
||||
|
||||
resultForCaller.put("jobStatus", jobStatus);
|
||||
LOG.info("Job status is " + jobStatus.getState() + " for " + jobUUID);
|
||||
|
||||
if(jobStatus.getState().equals(AsyncJobState.COMPLETE))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the job is complete, get the process result from state provider, and return it //
|
||||
// this output should look like it did if the job finished synchronously!! //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
Optional<ProcessState> processState = RunProcessAction.getStateProvider().get(ProcessState.class, new UUIDAndTypeStateKey(UUID.fromString(processUUID), StateType.PROCESS_STATUS));
|
||||
if(processState.isPresent())
|
||||
{
|
||||
RunProcessResult runProcessResult = new RunProcessResult(processState.get());
|
||||
serializeRunProcessResultForCaller(resultForCaller, runProcessResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
QJavalinImplementation.handleException(context, new RuntimeException("Could not find process results"));
|
||||
}
|
||||
}
|
||||
else if(jobStatus.getState().equals(AsyncJobState.ERROR))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the job had an error (e.g., a process step threw), "nicely" serialize its exception for the caller //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(jobStatus.getCaughtException() != null)
|
||||
{
|
||||
serializeRunProcessExceptionForCaller(resultForCaller, jobStatus.getCaughtException());
|
||||
}
|
||||
}
|
||||
|
||||
context.result(JsonUtils.toJson(resultForCaller));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setAsyncStepTimeoutMillis(int asyncStepTimeoutMillis)
|
||||
{
|
||||
ASYNC_STEP_TIMEOUT_MILLIS = asyncStepTimeoutMillis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Integer getTimeoutMillis(Context context)
|
||||
{
|
||||
Integer timeout = QJavalinImplementation.integerQueryParam(context, "_qStepTimeoutMillis");
|
||||
if(timeout == null)
|
||||
{
|
||||
timeout = ASYNC_STEP_TIMEOUT_MILLIS;
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
}
|
@ -27,18 +27,14 @@ import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import kong.unirest.HttpResponse;
|
||||
import kong.unirest.Unirest;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
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.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@ -51,39 +47,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
** and actually makes http requests into it.
|
||||
**
|
||||
*******************************************************************************/
|
||||
class QJavalinImplementationTest
|
||||
class QJavalinImplementationTest extends QJavalinTestBase
|
||||
{
|
||||
private static final int PORT = 6262;
|
||||
private static final String BASE_URL = "http://localhost:" + PORT;
|
||||
|
||||
private static final int MORE_THAN_TIMEOUT = 500;
|
||||
private static final int LESS_THAN_TIMEOUT = 50;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Before the class (all) runs, start a javalin server.
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeAll
|
||||
public static void beforeAll()
|
||||
{
|
||||
QJavalinImplementation qJavalinImplementation = new QJavalinImplementation(TestUtils.defineInstance());
|
||||
QJavalinImplementation.setAsyncStepTimeoutMillis(250);
|
||||
qJavalinImplementation.startJavalinServer(PORT);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fully rebuild the test-database before each test runs, for completely known state.
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
public void beforeEach() throws Exception
|
||||
{
|
||||
TestUtils.primeTestDatabase();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -406,293 +372,4 @@ class QJavalinImplementationTest
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a process
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processGreetInit()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/greet/init").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertNotNull(jsonObject);
|
||||
assertEquals("null X null", jsonObject.getJSONObject("values").getString("outputMessage"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a process with field values on the query string
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processGreetInitWithQueryValues()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/greet/init?greetingPrefix=Hey&greetingSuffix=Jude").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertNotNull(jsonObject);
|
||||
assertEquals("Hey X Jude", jsonObject.getJSONObject("values").getString("outputMessage"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test init'ing a process that goes async
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processInitGoingAsync() throws InterruptedException
|
||||
{
|
||||
String processBasePath = BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_SLEEP;
|
||||
HttpResponse<String> response = Unirest.get(processBasePath + "/init?" + TestUtils.SleeperStep.FIELD_SLEEP_MILLIS + "=" + MORE_THAN_TIMEOUT).asString();
|
||||
|
||||
JSONObject jsonObject = assertProcessStepWentAsyncResponse(response);
|
||||
String processUUID = jsonObject.getString("processUUID");
|
||||
String jobUUID = jsonObject.getString("jobUUID");
|
||||
assertNotNull(processUUID, "Process UUID should not be null.");
|
||||
assertNotNull(jobUUID, "Job UUID should not be null");
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// request job status before sleep is done //
|
||||
/////////////////////////////////////////////
|
||||
response = Unirest.get(processBasePath + "/" + processUUID + "/status/" + jobUUID).asString();
|
||||
jsonObject = assertProcessStepRunningResponse(response);
|
||||
|
||||
///////////////////////////////////
|
||||
// sleep, to let that job finish //
|
||||
///////////////////////////////////
|
||||
Thread.sleep(MORE_THAN_TIMEOUT);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// request job status again, get back results instead //
|
||||
////////////////////////////////////////////////////////
|
||||
response = Unirest.get(processBasePath + "/" + processUUID + "/status/" + jobUUID).asString();
|
||||
jsonObject = assertProcessStepCompleteResponse(response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test init'ing a process that does NOT goes async
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processInitNotGoingAsync()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_SLEEP + "/init?" + TestUtils.SleeperStep.FIELD_SLEEP_MILLIS + "=" + LESS_THAN_TIMEOUT)
|
||||
.header("Content-Type", "application/json").asString();
|
||||
assertProcessStepCompleteResponse(response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a step a process that goes async
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processStepGoingAsync() throws InterruptedException
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
// first init the process, to 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");
|
||||
String nextStep = jsonObject.getString("nextStep");
|
||||
assertNotNull(processUUID, "Process UUID should not be null.");
|
||||
assertNotNull(nextStep, "There should be a next step");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// second, run the 'nextStep' (the backend step, that sleeps). run it with a long enough sleep so that it'll go async //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
response = Unirest.post(processBasePath + "/" + processUUID + "/step/" + nextStep)
|
||||
.header("Content-Type", "application/json").asString();
|
||||
|
||||
jsonObject = assertProcessStepWentAsyncResponse(response);
|
||||
String jobUUID = jsonObject.getString("jobUUID");
|
||||
|
||||
///////////////////////////////////
|
||||
// sleep, to let that job finish //
|
||||
///////////////////////////////////
|
||||
Thread.sleep(MORE_THAN_TIMEOUT);
|
||||
|
||||
///////////////////////////////
|
||||
// third, request job status //
|
||||
///////////////////////////////
|
||||
response = Unirest.get(processBasePath + "/" + processUUID + "/status/" + jobUUID).asString();
|
||||
|
||||
jsonObject = assertProcessStepCompleteResponse(response);
|
||||
String nextStep2 = jsonObject.getString("nextStep");
|
||||
assertNotNull(nextStep2, "There be one more next step");
|
||||
assertNotEquals(nextStep, nextStep2, "The next step should be different this time.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a step a process that does NOT goes async
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processStepNotGoingAsync()
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
// first init the process, to 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 + "=" + LESS_THAN_TIMEOUT)
|
||||
.header("Content-Type", "application/json").asString();
|
||||
|
||||
JSONObject jsonObject = assertProcessStepCompleteResponse(response);
|
||||
String processUUID = jsonObject.getString("processUUID");
|
||||
String nextStep = jsonObject.getString("nextStep");
|
||||
assertNotNull(nextStep, "There should be a next step");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// second, run the 'nextStep' (the backend step, that sleeps). run it with a short enough sleep so that it won't go async //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
response = Unirest.post(processBasePath + "/" + processUUID + "/step/" + nextStep)
|
||||
.header("Content-Type", "application/json").asString();
|
||||
|
||||
jsonObject = assertProcessStepCompleteResponse(response);
|
||||
String nextStep2 = jsonObject.getString("nextStep");
|
||||
assertNotNull(nextStep2, "There be one more next step");
|
||||
assertNotEquals(nextStep, nextStep2, "The next step should be different this time.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test init'ing a process that goes async and then throws
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processInitGoingAsyncThenThrowing() throws InterruptedException
|
||||
{
|
||||
String processBasePath = BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_THROW;
|
||||
HttpResponse<String> response = Unirest.get(processBasePath + "/init?" + TestUtils.ThrowerStep.FIELD_SLEEP_MILLIS + "=" + MORE_THAN_TIMEOUT).asString();
|
||||
|
||||
JSONObject jsonObject = assertProcessStepWentAsyncResponse(response);
|
||||
String processUUID = jsonObject.getString("processUUID");
|
||||
String jobUUID = jsonObject.getString("jobUUID");
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// request job status before sleep is done //
|
||||
/////////////////////////////////////////////
|
||||
response = Unirest.get(processBasePath + "/" + processUUID + "/status/" + jobUUID).asString();
|
||||
jsonObject = assertProcessStepRunningResponse(response);
|
||||
|
||||
///////////////////////////////////
|
||||
// sleep, to let that job finish //
|
||||
///////////////////////////////////
|
||||
Thread.sleep(MORE_THAN_TIMEOUT);
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// request job status again, get back error status instead //
|
||||
/////////////////////////////////////////////////////////////
|
||||
response = Unirest.get(processBasePath + "/" + processUUID + "/status/" + jobUUID).asString();
|
||||
jsonObject = assertProcessStepErrorResponse(response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test init'ing a process that does NOT goes async, but throws.
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processInitNotGoingAsyncButThrowing()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_THROW + "/init?" + TestUtils.ThrowerStep.FIELD_SLEEP_MILLIS + "=" + LESS_THAN_TIMEOUT)
|
||||
.header("Content-Type", "application/json").asString();
|
||||
assertProcessStepErrorResponse(response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** every time a process step (sync or async) has gone async, expect what the
|
||||
** response should look like
|
||||
*******************************************************************************/
|
||||
private JSONObject assertProcessStepWentAsyncResponse(HttpResponse<String> response)
|
||||
{
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
|
||||
assertTrue(jsonObject.has("processUUID"), "Async-started response should have a processUUID");
|
||||
assertTrue(jsonObject.has("jobUUID"), "Async-started response should have a jobUUID");
|
||||
|
||||
assertFalse(jsonObject.has("values"), "Async-started response should NOT have values");
|
||||
assertFalse(jsonObject.has("error"), "Async-started response should NOT have error");
|
||||
|
||||
return (jsonObject);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** every time a process step (sync or async) is still running, expect certain things
|
||||
** to be (and not to be) in the json response.
|
||||
*******************************************************************************/
|
||||
private JSONObject assertProcessStepRunningResponse(HttpResponse<String> response)
|
||||
{
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
|
||||
assertTrue(jsonObject.has("jobStatus"), "Step Running response should have a jobStatus");
|
||||
|
||||
assertFalse(jsonObject.has("values"), "Step Running response should NOT have values");
|
||||
assertFalse(jsonObject.has("error"), "Step Running response should NOT have error");
|
||||
|
||||
assertEquals(AsyncJobState.RUNNING.name(), jsonObject.getJSONObject("jobStatus").getString("state"));
|
||||
|
||||
return (jsonObject);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** every time a process step (sync or async) completes, expect certain things
|
||||
** to be (and not to be) in the json response.
|
||||
*******************************************************************************/
|
||||
private JSONObject assertProcessStepCompleteResponse(HttpResponse<String> response)
|
||||
{
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
|
||||
assertTrue(jsonObject.has("values"), "Step Complete response should have values");
|
||||
|
||||
assertFalse(jsonObject.has("jobUUID"), "Step Complete response should not have a jobUUID");
|
||||
assertFalse(jsonObject.has("error"), "Step Complete response should not have an error");
|
||||
|
||||
return (jsonObject);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** every time a process step (sync or async) has an error, expect certain things
|
||||
** to be (and not to be) in the json response.
|
||||
*******************************************************************************/
|
||||
private JSONObject assertProcessStepErrorResponse(HttpResponse<String> response)
|
||||
{
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
|
||||
assertTrue(jsonObject.has("error"), "Step Error response should have an error");
|
||||
|
||||
assertFalse(jsonObject.has("jobUUID"), "Step Error response should not have a jobUUID");
|
||||
assertFalse(jsonObject.has("values"), "Step Error response should not have values");
|
||||
|
||||
return (jsonObject);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,398 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.javalin;
|
||||
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import kong.unirest.HttpResponse;
|
||||
import kong.unirest.Unirest;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for the javalin process handler methods.
|
||||
*******************************************************************************/
|
||||
class QJavalinProcessHandlerTest extends QJavalinTestBase
|
||||
{
|
||||
private static final int MORE_THAN_TIMEOUT = 500;
|
||||
private static final int LESS_THAN_TIMEOUT = 50;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a process
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processGreetInit()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/greet/init?recordsParam=recordIds&recordIds=2,3").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertNotNull(jsonObject);
|
||||
assertEquals("null X null", jsonObject.getJSONObject("values").getString("outputMessage"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a process that requires rows, but we didn't tell it how to get them.
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processRequiresRowsButNotSpecified()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/greet/init").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertNotNull(jsonObject);
|
||||
assertTrue(jsonObject.has("error"));
|
||||
assertTrue(jsonObject.getString("error").contains("missing input records"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a process and telling it rows to load via recordIds param
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processRequiresRowsWithRecordIdParam()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/greet/init?recordsParam=recordIds&recordIds=2,3").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertNotNull(jsonObject);
|
||||
|
||||
// todo - once we know how to get records from a process, add that call
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a process and telling it rows to load via filter JSON
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processRequiresRowsWithFilterJSON()
|
||||
{
|
||||
QQueryFilter queryFilter = new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria()
|
||||
.withFieldName("id")
|
||||
.withOperator(QCriteriaOperator.IN)
|
||||
.withValues(List.of(3, 4, 5)));
|
||||
String filterJSON = JsonUtils.toJson(queryFilter);
|
||||
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/greet/init?recordsParam=filterJSON&filterJSON=" + URLEncoder.encode(filterJSON, Charset.defaultCharset())).asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertNotNull(jsonObject);
|
||||
|
||||
// todo - once we know how to get records from a process, add that call
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a process with field values on the query string
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processGreetInitWithQueryValues()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/processes/greet/init?recordsParam=recordIds&recordIds=2,3&greetingPrefix=Hey&greetingSuffix=Jude").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
assertNotNull(jsonObject);
|
||||
assertEquals("Hey X Jude", jsonObject.getJSONObject("values").getString("outputMessage"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test init'ing a process that goes async
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processInitGoingAsync() throws InterruptedException
|
||||
{
|
||||
String processBasePath = BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_SLEEP;
|
||||
HttpResponse<String> response = Unirest.get(processBasePath + "/init?" + TestUtils.SleeperStep.FIELD_SLEEP_MILLIS + "=" + MORE_THAN_TIMEOUT).asString();
|
||||
|
||||
JSONObject jsonObject = assertProcessStepWentAsyncResponse(response);
|
||||
String processUUID = jsonObject.getString("processUUID");
|
||||
String jobUUID = jsonObject.getString("jobUUID");
|
||||
assertNotNull(processUUID, "Process UUID should not be null.");
|
||||
assertNotNull(jobUUID, "Job UUID should not be null");
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// request job status before sleep is done //
|
||||
/////////////////////////////////////////////
|
||||
response = Unirest.get(processBasePath + "/" + processUUID + "/status/" + jobUUID).asString();
|
||||
jsonObject = assertProcessStepRunningResponse(response);
|
||||
|
||||
///////////////////////////////////
|
||||
// sleep, to let that job finish //
|
||||
///////////////////////////////////
|
||||
Thread.sleep(MORE_THAN_TIMEOUT);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// request job status again, get back results instead //
|
||||
////////////////////////////////////////////////////////
|
||||
response = Unirest.get(processBasePath + "/" + processUUID + "/status/" + jobUUID).asString();
|
||||
jsonObject = assertProcessStepCompleteResponse(response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test init'ing a process that does NOT goes async
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processInitNotGoingAsync()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_SLEEP + "/init?" + TestUtils.SleeperStep.FIELD_SLEEP_MILLIS + "=" + LESS_THAN_TIMEOUT)
|
||||
.header("Content-Type", "application/json").asString();
|
||||
assertProcessStepCompleteResponse(response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a step a process that goes async
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processStepGoingAsync() throws InterruptedException
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
// first init the process, to 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");
|
||||
String nextStep = jsonObject.getString("nextStep");
|
||||
assertNotNull(processUUID, "Process UUID should not be null.");
|
||||
assertNotNull(nextStep, "There should be a next step");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// second, run the 'nextStep' (the backend step, that sleeps). run it with a long enough sleep so that it'll go async //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
response = Unirest.post(processBasePath + "/" + processUUID + "/step/" + nextStep)
|
||||
.header("Content-Type", "application/json").asString();
|
||||
|
||||
jsonObject = assertProcessStepWentAsyncResponse(response);
|
||||
String jobUUID = jsonObject.getString("jobUUID");
|
||||
|
||||
///////////////////////////////////
|
||||
// sleep, to let that job finish //
|
||||
///////////////////////////////////
|
||||
Thread.sleep(MORE_THAN_TIMEOUT);
|
||||
|
||||
///////////////////////////////
|
||||
// third, request job status //
|
||||
///////////////////////////////
|
||||
response = Unirest.get(processBasePath + "/" + processUUID + "/status/" + jobUUID).asString();
|
||||
|
||||
jsonObject = assertProcessStepCompleteResponse(response);
|
||||
String nextStep2 = jsonObject.getString("nextStep");
|
||||
assertNotNull(nextStep2, "There be one more next step");
|
||||
assertNotEquals(nextStep, nextStep2, "The next step should be different this time.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test running a step a process that does NOT goes async
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processStepNotGoingAsync()
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
// first init the process, to 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 + "=" + LESS_THAN_TIMEOUT)
|
||||
.header("Content-Type", "application/json").asString();
|
||||
|
||||
JSONObject jsonObject = assertProcessStepCompleteResponse(response);
|
||||
String processUUID = jsonObject.getString("processUUID");
|
||||
String nextStep = jsonObject.getString("nextStep");
|
||||
assertNotNull(nextStep, "There should be a next step");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// second, run the 'nextStep' (the backend step, that sleeps). run it with a short enough sleep so that it won't go async //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
response = Unirest.post(processBasePath + "/" + processUUID + "/step/" + nextStep)
|
||||
.header("Content-Type", "application/json").asString();
|
||||
|
||||
jsonObject = assertProcessStepCompleteResponse(response);
|
||||
String nextStep2 = jsonObject.getString("nextStep");
|
||||
assertNotNull(nextStep2, "There be one more next step");
|
||||
assertNotEquals(nextStep, nextStep2, "The next step should be different this time.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test init'ing a process that goes async and then throws
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processInitGoingAsyncThenThrowing() throws InterruptedException
|
||||
{
|
||||
String processBasePath = BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_THROW;
|
||||
HttpResponse<String> response = Unirest.get(processBasePath + "/init?" + TestUtils.ThrowerStep.FIELD_SLEEP_MILLIS + "=" + MORE_THAN_TIMEOUT).asString();
|
||||
|
||||
JSONObject jsonObject = assertProcessStepWentAsyncResponse(response);
|
||||
String processUUID = jsonObject.getString("processUUID");
|
||||
String jobUUID = jsonObject.getString("jobUUID");
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// request job status before sleep is done //
|
||||
/////////////////////////////////////////////
|
||||
response = Unirest.get(processBasePath + "/" + processUUID + "/status/" + jobUUID).asString();
|
||||
jsonObject = assertProcessStepRunningResponse(response);
|
||||
|
||||
///////////////////////////////////
|
||||
// sleep, to let that job finish //
|
||||
///////////////////////////////////
|
||||
Thread.sleep(MORE_THAN_TIMEOUT);
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// request job status again, get back error status instead //
|
||||
/////////////////////////////////////////////////////////////
|
||||
response = Unirest.get(processBasePath + "/" + processUUID + "/status/" + jobUUID).asString();
|
||||
jsonObject = assertProcessStepErrorResponse(response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** test init'ing a process that does NOT goes async, but throws.
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void test_processInitNotGoingAsyncButThrowing()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(BASE_URL + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_THROW + "/init?" + TestUtils.ThrowerStep.FIELD_SLEEP_MILLIS + "=" + LESS_THAN_TIMEOUT)
|
||||
.header("Content-Type", "application/json").asString();
|
||||
assertProcessStepErrorResponse(response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** every time a process step (sync or async) has gone async, expect what the
|
||||
** response should look like
|
||||
*******************************************************************************/
|
||||
private JSONObject assertProcessStepWentAsyncResponse(HttpResponse<String> response)
|
||||
{
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
|
||||
assertTrue(jsonObject.has("processUUID"), "Async-started response should have a processUUID");
|
||||
assertTrue(jsonObject.has("jobUUID"), "Async-started response should have a jobUUID");
|
||||
|
||||
assertFalse(jsonObject.has("values"), "Async-started response should NOT have values");
|
||||
assertFalse(jsonObject.has("error"), "Async-started response should NOT have error");
|
||||
|
||||
return (jsonObject);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** every time a process step (sync or async) is still running, expect certain things
|
||||
** to be (and not to be) in the json response.
|
||||
*******************************************************************************/
|
||||
private JSONObject assertProcessStepRunningResponse(HttpResponse<String> response)
|
||||
{
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
|
||||
assertTrue(jsonObject.has("jobStatus"), "Step Running response should have a jobStatus");
|
||||
|
||||
assertFalse(jsonObject.has("values"), "Step Running response should NOT have values");
|
||||
assertFalse(jsonObject.has("error"), "Step Running response should NOT have error");
|
||||
|
||||
assertEquals(AsyncJobState.RUNNING.name(), jsonObject.getJSONObject("jobStatus").getString("state"));
|
||||
|
||||
return (jsonObject);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** every time a process step (sync or async) completes, expect certain things
|
||||
** to be (and not to be) in the json response.
|
||||
*******************************************************************************/
|
||||
private JSONObject assertProcessStepCompleteResponse(HttpResponse<String> response)
|
||||
{
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
|
||||
assertTrue(jsonObject.has("values"), "Step Complete response should have values");
|
||||
|
||||
assertFalse(jsonObject.has("jobUUID"), "Step Complete response should not have a jobUUID");
|
||||
assertFalse(jsonObject.has("error"), "Step Complete response should not have an error");
|
||||
|
||||
return (jsonObject);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** every time a process step (sync or async) has an error, expect certain things
|
||||
** to be (and not to be) in the json response.
|
||||
*******************************************************************************/
|
||||
private JSONObject assertProcessStepErrorResponse(HttpResponse<String> response)
|
||||
{
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
|
||||
|
||||
assertTrue(jsonObject.has("error"), "Step Error response should have an error");
|
||||
|
||||
assertFalse(jsonObject.has("jobUUID"), "Step Error response should not have a jobUUID");
|
||||
assertFalse(jsonObject.has("values"), "Step Error response should not have values");
|
||||
|
||||
return (jsonObject);
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.javalin;
|
||||
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** base class for javalin implementation tests.
|
||||
*******************************************************************************/
|
||||
public class QJavalinTestBase
|
||||
{
|
||||
private static final int PORT = 6262;
|
||||
protected static final String BASE_URL = "http://localhost:" + PORT;
|
||||
|
||||
private static QJavalinImplementation qJavalinImplementation;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Before the class (all) runs, start a javalin server.
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeAll
|
||||
public static void beforeAll()
|
||||
{
|
||||
qJavalinImplementation = new QJavalinImplementation(TestUtils.defineInstance());
|
||||
QJavalinProcessHandler.setAsyncStepTimeoutMillis(250);
|
||||
qJavalinImplementation.startJavalinServer(PORT);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Before the class (all) runs, start a javalin server.
|
||||
**
|
||||
*******************************************************************************/
|
||||
@AfterAll
|
||||
public static void afterAll()
|
||||
{
|
||||
qJavalinImplementation.stopJavalinServer();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fully rebuild the test-database before each test runs, for completely known state.
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
public void beforeEach() throws Exception
|
||||
{
|
||||
TestUtils.primeTestDatabase();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user