mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-19 05:30:43 +00:00
Merge branch 'feature/CTLE-422-api-for-scripts' into integration/sprint-25
# Conflicts: # qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
This commit is contained in:
@ -51,10 +51,18 @@ commands:
|
|||||||
module: qqq-backend-module-filesystem
|
module: qqq-backend-module-filesystem
|
||||||
- store_jacoco_site:
|
- store_jacoco_site:
|
||||||
module: qqq-backend-module-rdbms
|
module: qqq-backend-module-rdbms
|
||||||
|
- store_jacoco_site:
|
||||||
|
module: qqq-backend-module-api
|
||||||
|
- store_jacoco_site:
|
||||||
|
module: qqq-middleware-api
|
||||||
- store_jacoco_site:
|
- store_jacoco_site:
|
||||||
module: qqq-middleware-javalin
|
module: qqq-middleware-javalin
|
||||||
- store_jacoco_site:
|
- store_jacoco_site:
|
||||||
module: qqq-middleware-picocli
|
module: qqq-middleware-picocli
|
||||||
|
- store_jacoco_site:
|
||||||
|
module: qqq-middleware-slack
|
||||||
|
- store_jacoco_site:
|
||||||
|
module: qqq-language-support-javascript
|
||||||
- store_jacoco_site:
|
- store_jacoco_site:
|
||||||
module: qqq-sample-project
|
module: qqq-sample-project
|
||||||
- run:
|
- run:
|
||||||
|
@ -278,6 +278,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
|||||||
.withPriority(record.getValueInteger("priority"))
|
.withPriority(record.getValueInteger("priority"))
|
||||||
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class))
|
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class))
|
||||||
.withValues(MapBuilder.of("scriptId", record.getValue("scriptId")))
|
.withValues(MapBuilder.of("scriptId", record.getValue("scriptId")))
|
||||||
|
.withIncludeRecordAssociations(true)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -392,6 +393,8 @@ public class PollingAutomationPerTableRunner implements Runnable
|
|||||||
|
|
||||||
queryInput.setFilter(filter);
|
queryInput.setFilter(filter);
|
||||||
|
|
||||||
|
queryInput.setIncludeAssociations(action.getIncludeRecordAssociations());
|
||||||
|
|
||||||
return (new QueryAction().execute(queryInput).getRecords());
|
return (new QueryAction().execute(queryInput).getRecords());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,4 +50,13 @@ public interface DeleteInterface
|
|||||||
return (false);
|
return (false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Specify whether this particular module's delete action can & should fetch
|
||||||
|
** records before deleting them, e.g., for audits or "not-found-checks"
|
||||||
|
*******************************************************************************/
|
||||||
|
default boolean supportsPreFetchQuery()
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,4 +37,14 @@ public interface UpdateInterface
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
UpdateOutput execute(UpdateInput updateInput) throws QException;
|
UpdateOutput execute(UpdateInput updateInput) throws QException;
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Specify whether this particular module's update action can & should fetch
|
||||||
|
** records before updating them, e.g., for audits or "not-found-checks"
|
||||||
|
*******************************************************************************/
|
||||||
|
default boolean supportsPreFetchQuery()
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.actions.reporting;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ public class BufferedRecordPipe extends RecordPipe
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
public void addRecord(QRecord record)
|
public void addRecord(QRecord record) throws QException
|
||||||
{
|
{
|
||||||
buffer.add(record);
|
buffer.add(record);
|
||||||
if(buffer.size() >= bufferSize)
|
if(buffer.size() >= bufferSize)
|
||||||
@ -78,7 +79,7 @@ public class BufferedRecordPipe extends RecordPipe
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void finalFlush()
|
public void finalFlush() throws QException
|
||||||
{
|
{
|
||||||
if(!buffer.isEmpty())
|
if(!buffer.isEmpty())
|
||||||
{
|
{
|
||||||
|
@ -26,10 +26,11 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Consumer;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeConsumer;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -47,7 +48,7 @@ public class RecordPipe
|
|||||||
|
|
||||||
private boolean isTerminated = false;
|
private boolean isTerminated = false;
|
||||||
|
|
||||||
private Consumer<List<QRecord>> postRecordActions = null;
|
private UnsafeConsumer<List<QRecord>, QException> postRecordActions = null;
|
||||||
|
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
// See usage below for explanation //
|
// See usage below for explanation //
|
||||||
@ -93,7 +94,7 @@ public class RecordPipe
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Add a record to the pipe. Will block if the pipe is full. Will noop if pipe is terminated.
|
** Add a record to the pipe. Will block if the pipe is full. Will noop if pipe is terminated.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void addRecord(QRecord record)
|
public void addRecord(QRecord record) throws QException
|
||||||
{
|
{
|
||||||
if(isTerminated)
|
if(isTerminated)
|
||||||
{
|
{
|
||||||
@ -109,7 +110,7 @@ public class RecordPipe
|
|||||||
// (which we'll create as a field in this class, to avoid always re-constructing) //
|
// (which we'll create as a field in this class, to avoid always re-constructing) //
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
singleRecordListForPostRecordActions.add(record);
|
singleRecordListForPostRecordActions.add(record);
|
||||||
postRecordActions.accept(singleRecordListForPostRecordActions);
|
postRecordActions.run(singleRecordListForPostRecordActions);
|
||||||
record = singleRecordListForPostRecordActions.remove(0);
|
record = singleRecordListForPostRecordActions.remove(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,11 +153,11 @@ public class RecordPipe
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Add a list of records to the pipe. Will block if the pipe is full. Will noop if pipe is terminated.
|
** Add a list of records to the pipe. Will block if the pipe is full. Will noop if pipe is terminated.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void addRecords(List<QRecord> records)
|
public void addRecords(List<QRecord> records) throws QException
|
||||||
{
|
{
|
||||||
if(postRecordActions != null)
|
if(postRecordActions != null)
|
||||||
{
|
{
|
||||||
postRecordActions.accept(records);
|
postRecordActions.run(records);
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -207,7 +208,7 @@ public class RecordPipe
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void setPostRecordActions(Consumer<List<QRecord>> postRecordActions)
|
public void setPostRecordActions(UnsafeConsumer<List<QRecord>, QException> postRecordActions)
|
||||||
{
|
{
|
||||||
this.postRecordActions = postRecordActions;
|
this.postRecordActions = postRecordActions;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.actions.reporting;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Subclass of BufferedRecordPipe, which ultimately sends records down to an
|
||||||
|
** original RecordPipe.
|
||||||
|
**
|
||||||
|
** Meant to be used where: someone passed in a RecordPipe (so they have a reference
|
||||||
|
** to it, and they are waiting to read from it), but the producer knows that
|
||||||
|
** it will be better to buffer the records, so they want to use a buffered pipe
|
||||||
|
** (but they still need the records to end up in the original pipe - thus -
|
||||||
|
** it gets wrapped by an object of this class).
|
||||||
|
*******************************************************************************/
|
||||||
|
public class RecordPipeBufferedWrapper extends BufferedRecordPipe
|
||||||
|
{
|
||||||
|
private RecordPipe wrappedPipe;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor - uses default buffer size
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public RecordPipeBufferedWrapper(RecordPipe wrappedPipe)
|
||||||
|
{
|
||||||
|
this.wrappedPipe = wrappedPipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor - customize buffer size.
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public RecordPipeBufferedWrapper(Integer bufferSize, RecordPipe wrappedPipe)
|
||||||
|
{
|
||||||
|
super(bufferSize);
|
||||||
|
this.wrappedPipe = wrappedPipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** when it's time to actually add records into the pipe, actually add them
|
||||||
|
** into the wrapped pipe!
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void addRecords(List<QRecord> records) throws QException
|
||||||
|
{
|
||||||
|
wrappedPipe.addRecords(records);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.actions.scripts;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface AssociatedScriptContextPrimerInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
void primeContext(ExecuteCodeInput executeCodeInput, ScriptRevision scriptRevision) throws QException;
|
||||||
|
|
||||||
|
}
|
@ -25,13 +25,21 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.Log4jCodeExecutionLogger;
|
import com.kingsrook.qqq.backend.core.actions.scripts.logging.Log4jCodeExecutionLogger;
|
||||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.scripts.logging.ScriptExecutionLoggerInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
|
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.scripts.AbstractRunScriptInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
|
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -49,6 +57,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class ExecuteCodeAction
|
public class ExecuteCodeAction
|
||||||
{
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(ExecuteCodeAction.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
@ -68,10 +79,10 @@ public class ExecuteCodeAction
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
String languageExecutor = switch(codeReference.getCodeType())
|
String languageExecutor = switch(codeReference.getCodeType())
|
||||||
{
|
{
|
||||||
case JAVA -> "com.kingsrook.qqq.backend.core.actions.scripts.QJavaExecutor";
|
case JAVA -> "com.kingsrook.qqq.backend.core.actions.scripts.QJavaExecutor";
|
||||||
case JAVA_SCRIPT -> "com.kingsrook.qqq.languages.javascript.QJavaScriptExecutor";
|
case JAVA_SCRIPT -> "com.kingsrook.qqq.languages.javascript.QJavaScriptExecutor";
|
||||||
};
|
};
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Class<? extends QCodeExecutor> executorClass = (Class<? extends QCodeExecutor>) Class.forName(languageExecutor);
|
Class<? extends QCodeExecutor> executorClass = (Class<? extends QCodeExecutor>) Class.forName(languageExecutor);
|
||||||
@ -108,6 +119,91 @@ public class ExecuteCodeAction
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static ExecuteCodeInput setupExecuteCodeInput(AbstractRunScriptInput<?> input, ScriptRevision scriptRevision)
|
||||||
|
{
|
||||||
|
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput();
|
||||||
|
executeCodeInput.setInput(new HashMap<>(Objects.requireNonNullElseGet(input.getInputValues(), HashMap::new)));
|
||||||
|
executeCodeInput.setContext(new HashMap<>());
|
||||||
|
|
||||||
|
Map<String, Serializable> context = executeCodeInput.getContext();
|
||||||
|
if(input.getOutputObject() != null)
|
||||||
|
{
|
||||||
|
context.put("output", input.getOutputObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(input.getScriptUtils() != null)
|
||||||
|
{
|
||||||
|
context.put("scriptUtils", input.getScriptUtils());
|
||||||
|
}
|
||||||
|
|
||||||
|
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
|
||||||
|
|
||||||
|
ExecuteCodeAction.addApiUtilityToContext(context, scriptRevision);
|
||||||
|
ExecuteCodeAction.setExecutionLoggerInExecuteCodeInput(input, scriptRevision, executeCodeInput);
|
||||||
|
|
||||||
|
return (executeCodeInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Try to (dynamically) load the ApiScriptUtils object from the api middleware
|
||||||
|
** module -- in case the runtime doesn't have that module deployed (e.g, not in
|
||||||
|
** the project pom).
|
||||||
|
*******************************************************************************/
|
||||||
|
public static void addApiUtilityToContext(Map<String, Serializable> context, ScriptRevision scriptRevision)
|
||||||
|
{
|
||||||
|
if(!StringUtils.hasContent(scriptRevision.getApiName()) || !StringUtils.hasContent(scriptRevision.getApiVersion()))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class<?> apiScriptUtilsClass = Class.forName("com.kingsrook.qqq.api.utils.ApiScriptUtils");
|
||||||
|
Object apiScriptUtilsObject = apiScriptUtilsClass.getConstructor(String.class, String.class).newInstance(scriptRevision.getApiName(), scriptRevision.getApiVersion());
|
||||||
|
context.put("api", (Serializable) apiScriptUtilsObject);
|
||||||
|
}
|
||||||
|
catch(ClassNotFoundException e)
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this is the only exception we're kinda expecting here - so catch for it specifically, and just log.trace - others, warn //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
LOG.trace("Couldn't load ApiScriptUtils class - qqq-middleware-api not on the classpath?");
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error adding api utility to script context", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static void setExecutionLoggerInExecuteCodeInput(AbstractRunScriptInput<?> input, ScriptRevision scriptRevision, ExecuteCodeInput executeCodeInput)
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// let caller supply a logger, or by default use StoreScriptLogAndScriptLogLineExecutionLogger //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
QCodeExecutionLoggerInterface executionLogger = Objects.requireNonNullElseGet(input.getLogger(), () -> new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId()));
|
||||||
|
executeCodeInput.setExecutionLogger(executionLogger);
|
||||||
|
if(executionLogger instanceof ScriptExecutionLoggerInterface scriptExecutionLoggerInterface)
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if logger is aware of scripts (as opposed to a generic CodeExecution logger), give it the ids. //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
scriptExecutionLoggerInterface.setScriptId(scriptRevision.getScriptId());
|
||||||
|
scriptExecutionLoggerInterface.setScriptRevisionId(scriptRevision.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -22,15 +22,14 @@
|
|||||||
package com.kingsrook.qqq.backend.core.actions.scripts;
|
package com.kingsrook.qqq.backend.core.actions.scripts;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.ScriptExecutionLoggerInterface;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
@ -49,8 +48,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||||
@ -96,6 +93,7 @@ public class RunAdHocRecordScriptAction
|
|||||||
QueryInput queryInput = new QueryInput();
|
QueryInput queryInput = new QueryInput();
|
||||||
queryInput.setTableName(input.getTableName());
|
queryInput.setTableName(input.getTableName());
|
||||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getRecordPrimaryKeyList())));
|
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getRecordPrimaryKeyList())));
|
||||||
|
queryInput.setIncludeAssociations(true);
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
input.setRecordList(queryOutput.getRecords());
|
input.setRecordList(queryOutput.getRecords());
|
||||||
}
|
}
|
||||||
@ -112,43 +110,14 @@ public class RunAdHocRecordScriptAction
|
|||||||
/////////////
|
/////////////
|
||||||
// run it! //
|
// run it! //
|
||||||
/////////////
|
/////////////
|
||||||
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput();
|
ExecuteCodeInput executeCodeInput = ExecuteCodeAction.setupExecuteCodeInput(input, scriptRevision);
|
||||||
executeCodeInput.setInput(new HashMap<>(Objects.requireNonNullElseGet(input.getInputValues(), HashMap::new)));
|
executeCodeInput.getInput().put("records", getRecordsForScript(input, scriptRevision));
|
||||||
executeCodeInput.getInput().put("records", new ArrayList<>(input.getRecordList()));
|
|
||||||
executeCodeInput.setContext(new HashMap<>());
|
|
||||||
if(input.getOutputObject() != null)
|
|
||||||
{
|
|
||||||
executeCodeInput.getContext().put("output", input.getOutputObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(input.getScriptUtils() != null)
|
|
||||||
{
|
|
||||||
executeCodeInput.getContext().put("scriptUtils", input.getScriptUtils());
|
|
||||||
}
|
|
||||||
|
|
||||||
executeCodeInput.getContext().put("api", new ScriptApi());
|
|
||||||
|
|
||||||
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// let caller supply a logger, or by default use StoreScriptLogAndScriptLogLineExecutionLogger //
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
QCodeExecutionLoggerInterface executionLogger = Objects.requireNonNullElseGet(input.getLogger(), () -> new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId()));
|
|
||||||
executeCodeInput.setExecutionLogger(executionLogger);
|
|
||||||
if(executionLogger instanceof ScriptExecutionLoggerInterface scriptExecutionLoggerInterface)
|
|
||||||
{
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// if logger is aware of scripts (as opposed to a generic CodeExecution logger), give it the ids. //
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
scriptExecutionLoggerInterface.setScriptId(scriptRevision.getScriptId());
|
|
||||||
scriptExecutionLoggerInterface.setScriptRevisionId(scriptRevision.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
|
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
|
||||||
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
|
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
|
||||||
|
|
||||||
output.setOutput(executeCodeOutput.getOutput());
|
output.setOutput(executeCodeOutput.getOutput());
|
||||||
output.setLogger(executionLogger);
|
output.setLogger(executeCodeInput.getExecutionLogger());
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
@ -158,6 +127,37 @@ public class RunAdHocRecordScriptAction
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static ArrayList<? extends Serializable> getRecordsForScript(RunAdHocRecordScriptInput input, ScriptRevision scriptRevision)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class<?> apiScriptUtilsClass = Class.forName("com.kingsrook.qqq.api.utils.ApiScriptUtils");
|
||||||
|
Method qRecordListToApiRecordList = apiScriptUtilsClass.getMethod("qRecordListToApiRecordList", List.class, String.class, String.class, String.class);
|
||||||
|
Object apiRecordList = qRecordListToApiRecordList.invoke(null, input.getRecordList(), input.getTableName(), scriptRevision.getApiName(), scriptRevision.getApiVersion());
|
||||||
|
|
||||||
|
// noinspection unchecked
|
||||||
|
return (ArrayList<? extends Serializable>) apiRecordList;
|
||||||
|
}
|
||||||
|
catch(ClassNotFoundException e)
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this is the only exception we're kinda expecting here - so catch for it specifically, and just log.trace - others, warn //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
LOG.trace("Couldn't load ApiScriptUtils class - qqq-middleware-api not on the classpath?");
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error converting QRecord list to api record list", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (new ArrayList<>(input.getRecordList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -25,11 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.ScriptExecutionLoggerInterface;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||||
@ -40,8 +36,6 @@ import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptO
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||||
|
|
||||||
@ -54,6 +48,7 @@ public class RunAssociatedScriptAction
|
|||||||
private Map<AssociatedScriptCodeReference, ScriptRevision> scriptRevisionCache = new HashMap<>();
|
private Map<AssociatedScriptCodeReference, ScriptRevision> scriptRevisionCache = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -61,35 +56,12 @@ public class RunAssociatedScriptAction
|
|||||||
{
|
{
|
||||||
ActionHelper.validateSession(input);
|
ActionHelper.validateSession(input);
|
||||||
|
|
||||||
ScriptRevision scriptRevision = getScriptRevision(input);
|
ScriptRevision scriptRevision = getScriptRevision(input);
|
||||||
|
ExecuteCodeInput executeCodeInput = ExecuteCodeAction.setupExecuteCodeInput(input, scriptRevision);
|
||||||
|
|
||||||
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput();
|
if(input.getAssociatedScriptContextPrimerInterface() != null)
|
||||||
executeCodeInput.setInput(new HashMap<>(input.getInputValues()));
|
|
||||||
executeCodeInput.setContext(new HashMap<>());
|
|
||||||
if(input.getOutputObject() != null)
|
|
||||||
{
|
{
|
||||||
executeCodeInput.getContext().put("output", input.getOutputObject());
|
input.getAssociatedScriptContextPrimerInterface().primeContext(executeCodeInput, scriptRevision);
|
||||||
}
|
|
||||||
|
|
||||||
if(input.getScriptUtils() != null)
|
|
||||||
{
|
|
||||||
executeCodeInput.getContext().put("scriptUtils", input.getScriptUtils());
|
|
||||||
}
|
|
||||||
|
|
||||||
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// let caller supply a logger, or by default use StoreScriptLogAndScriptLogLineExecutionLogger //
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
QCodeExecutionLoggerInterface executionLogger = Objects.requireNonNullElseGet(input.getLogger(), () -> new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId()));
|
|
||||||
executeCodeInput.setExecutionLogger(executionLogger);
|
|
||||||
if(executionLogger instanceof ScriptExecutionLoggerInterface scriptExecutionLoggerInterface)
|
|
||||||
{
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// if logger is aware of scripts (as opposed to a generic CodeExecution logger), give it the ids. //
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
scriptExecutionLoggerInterface.setScriptId(scriptRevision.getScriptId());
|
|
||||||
scriptExecutionLoggerInterface.setScriptRevisionId(scriptRevision.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
|
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
|
||||||
|
@ -47,6 +47,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Object made available to scripts for access to qqq api (e.g., query, insert,
|
** Object made available to scripts for access to qqq api (e.g., query, insert,
|
||||||
** etc, plus object constructors).
|
** etc, plus object constructors).
|
||||||
|
**
|
||||||
|
** Before scripts knew about the API, this class made sense and was used.
|
||||||
|
** But, now that scripts do know about the API, it feels like this class could
|
||||||
|
** be deleted... but, what about, a QQQ deployment without the API module...
|
||||||
|
** In that case, we might still want this class... think about it.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class ScriptApi implements Serializable
|
public class ScriptApi implements Serializable
|
||||||
{
|
{
|
||||||
|
@ -184,6 +184,8 @@ public class StoreAssociatedScriptAction
|
|||||||
QRecord scriptRevision = new QRecord()
|
QRecord scriptRevision = new QRecord()
|
||||||
.withValue("scriptId", script.getValue("id"))
|
.withValue("scriptId", script.getValue("id"))
|
||||||
.withValue("contents", input.getCode())
|
.withValue("contents", input.getCode())
|
||||||
|
.withValue("apiName", input.getApiName())
|
||||||
|
.withValue("apiVersion", input.getApiVersion())
|
||||||
.withValue("commitMessage", commitMessage)
|
.withValue("commitMessage", commitMessage)
|
||||||
.withValue("sequenceNo", nextSequenceNo);
|
.withValue("sequenceNo", nextSequenceNo);
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptInput;
|
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -47,7 +48,7 @@ public interface TestScriptActionInterface
|
|||||||
** Note - such a method may want or need to put an "output" object into the
|
** Note - such a method may want or need to put an "output" object into the
|
||||||
** executeCodeInput's context map.
|
** executeCodeInput's context map.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
void setupTestScriptInput(TestScriptInput testScriptInput, ExecuteCodeInput executeCodeInput);
|
void setupTestScriptInput(TestScriptInput testScriptInput, ExecuteCodeInput executeCodeInput) throws QException;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -87,12 +88,21 @@ public interface TestScriptActionInterface
|
|||||||
BuildScriptLogAndScriptLogLineExecutionLogger executionLogger = new BuildScriptLogAndScriptLogLineExecutionLogger(null, null);
|
BuildScriptLogAndScriptLogLineExecutionLogger executionLogger = new BuildScriptLogAndScriptLogLineExecutionLogger(null, null);
|
||||||
executeCodeInput.setExecutionLogger(executionLogger);
|
executeCodeInput.setExecutionLogger(executionLogger);
|
||||||
|
|
||||||
setupTestScriptInput(input, executeCodeInput);
|
|
||||||
|
|
||||||
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
setupTestScriptInput(input, executeCodeInput);
|
||||||
|
|
||||||
|
ScriptRevision scriptRevision = new ScriptRevision().withApiName(input.getApiName()).withApiVersion(input.getApiVersion());
|
||||||
|
|
||||||
|
if(this instanceof AssociatedScriptContextPrimerInterface associatedScriptContextPrimerInterface)
|
||||||
|
{
|
||||||
|
associatedScriptContextPrimerInterface.primeContext(executeCodeInput, scriptRevision);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
|
||||||
|
|
||||||
|
ExecuteCodeAction.addApiUtilityToContext(executeCodeInput.getContext(), scriptRevision);
|
||||||
|
|
||||||
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
|
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
|
||||||
output.setOutputObject(processTestScriptOutput(executeCodeOutput));
|
output.setOutputObject(processTestScriptOutput(executeCodeOutput));
|
||||||
}
|
}
|
||||||
|
@ -76,13 +76,13 @@ public class DeleteAction
|
|||||||
|
|
||||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend());
|
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend());
|
||||||
|
DeleteInterface deleteInterface = qModule.getDeleteInterface();
|
||||||
|
|
||||||
if(CollectionUtils.nullSafeHasContents(deleteInput.getPrimaryKeys()) && deleteInput.getQueryFilter() != null)
|
if(CollectionUtils.nullSafeHasContents(deleteInput.getPrimaryKeys()) && deleteInput.getQueryFilter() != null)
|
||||||
{
|
{
|
||||||
throw (new QException("A delete request may not contain both a list of primary keys and a query filter."));
|
throw (new QException("A delete request may not contain both a list of primary keys and a query filter."));
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteInterface deleteInterface = qModule.getDeleteInterface();
|
|
||||||
if(deleteInput.getQueryFilter() != null && !deleteInterface.supportsQueryFilterInput())
|
if(deleteInput.getQueryFilter() != null && !deleteInterface.supportsQueryFilterInput())
|
||||||
{
|
{
|
||||||
LOG.info("Querying for primary keys, for backend module " + qModule.getBackendType() + " which does not support queryFilter input for deletes");
|
LOG.info("Querying for primary keys, for backend module " + qModule.getBackendType() + " which does not support queryFilter input for deletes");
|
||||||
@ -99,8 +99,8 @@ public class DeleteAction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<QRecord> recordListForAudit = getRecordListForAuditIfNeeded(deleteInput);
|
List<QRecord> recordListForAudit = deleteInterface.supportsPreFetchQuery() ? getRecordListForAuditIfNeeded(deleteInput) : new ArrayList<>();
|
||||||
List<QRecord> recordsWithValidationErrors = validateRecordsExistAndCanBeAccessed(deleteInput, recordListForAudit);
|
List<QRecord> recordsWithValidationErrors = deleteInterface.supportsPreFetchQuery() ? validateRecordsExistAndCanBeAccessed(deleteInput, recordListForAudit) : new ArrayList<>();
|
||||||
|
|
||||||
DeleteOutput deleteOutput = deleteInterface.execute(deleteInput);
|
DeleteOutput deleteOutput = deleteInterface.execute(deleteInput);
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCusto
|
|||||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.BufferedRecordPipe;
|
import com.kingsrook.qqq.backend.core.actions.reporting.BufferedRecordPipe;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipeBufferedWrapper;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
@ -85,14 +86,15 @@ public class QueryAction
|
|||||||
if(queryInput.getRecordPipe() != null)
|
if(queryInput.getRecordPipe() != null)
|
||||||
{
|
{
|
||||||
queryInput.getRecordPipe().setPostRecordActions(this::postRecordActions);
|
queryInput.getRecordPipe().setPostRecordActions(this::postRecordActions);
|
||||||
}
|
|
||||||
|
|
||||||
if(queryInput.getIncludeAssociations() && queryInput.getRecordPipe() != null)
|
if(queryInput.getIncludeAssociations())
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// todo - support this in the future maybe? //
|
// if the user requested to include associations, it's important that that is buffered, //
|
||||||
//////////////////////////////////////////////
|
// (for performance reasons), so, wrap the user's pipe with a buffer //
|
||||||
throw (new QException("Associations may not be fetched into a RecordPipe."));
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
queryInput.setRecordPipe(new RecordPipeBufferedWrapper(queryInput.getRecordPipe()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||||
@ -111,11 +113,6 @@ public class QueryAction
|
|||||||
postRecordActions(queryOutput.getRecords());
|
postRecordActions(queryOutput.getRecords());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(queryInput.getIncludeAssociations())
|
|
||||||
{
|
|
||||||
manageAssociations(queryInput, queryOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
return queryOutput;
|
return queryOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,8 +121,9 @@ public class QueryAction
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void manageAssociations(QueryInput queryInput, QueryOutput queryOutput) throws QException
|
private void manageAssociations(QueryInput queryInput, List<QRecord> queryOutputRecords) throws QException
|
||||||
{
|
{
|
||||||
|
LOG.info("In manageAssociations for " + queryInput.getTableName() + " with " + queryOutputRecords.size() + " records");
|
||||||
QTableMetaData table = queryInput.getTable();
|
QTableMetaData table = queryInput.getTable();
|
||||||
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||||
{
|
{
|
||||||
@ -149,7 +147,7 @@ public class QueryAction
|
|||||||
{
|
{
|
||||||
JoinOn joinOn = join.getJoinOns().get(0);
|
JoinOn joinOn = join.getJoinOns().get(0);
|
||||||
Set<Serializable> values = new HashSet<>();
|
Set<Serializable> values = new HashSet<>();
|
||||||
for(QRecord record : queryOutput.getRecords())
|
for(QRecord record : queryOutputRecords)
|
||||||
{
|
{
|
||||||
Serializable value = record.getValue(joinOn.getLeftField());
|
Serializable value = record.getValue(joinOn.getLeftField());
|
||||||
values.add(value);
|
values.add(value);
|
||||||
@ -161,7 +159,7 @@ public class QueryAction
|
|||||||
{
|
{
|
||||||
filter.setBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
filter.setBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
||||||
|
|
||||||
for(QRecord record : queryOutput.getRecords())
|
for(QRecord record : queryOutputRecords)
|
||||||
{
|
{
|
||||||
QQueryFilter subFilter = new QQueryFilter();
|
QQueryFilter subFilter = new QQueryFilter();
|
||||||
filter.addSubFilter(subFilter);
|
filter.addSubFilter(subFilter);
|
||||||
@ -229,7 +227,7 @@ public class QueryAction
|
|||||||
** not one created via List.of()). This may include setting display values,
|
** not one created via List.of()). This may include setting display values,
|
||||||
** translating possible values, and running post-record customizations.
|
** translating possible values, and running post-record customizations.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void postRecordActions(List<QRecord> records)
|
public void postRecordActions(List<QRecord> records) throws QException
|
||||||
{
|
{
|
||||||
if(this.postQueryRecordCustomizer.isPresent())
|
if(this.postQueryRecordCustomizer.isPresent())
|
||||||
{
|
{
|
||||||
@ -250,6 +248,11 @@ public class QueryAction
|
|||||||
QValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), records);
|
QValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), records);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(queryInput.getIncludeAssociations())
|
||||||
|
{
|
||||||
|
manageAssociations(queryInput, records);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// mask any password fields //
|
// mask any password fields //
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
|
@ -33,6 +33,7 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
|
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
@ -87,18 +88,24 @@ public class UpdateAction
|
|||||||
ValueBehaviorApplier.applyFieldBehaviors(updateInput.getInstance(), updateInput.getTable(), updateInput.getRecords());
|
ValueBehaviorApplier.applyFieldBehaviors(updateInput.getInstance(), updateInput.getTable(), updateInput.getRecords());
|
||||||
// todo - need to handle records with errors coming out of here...
|
// todo - need to handle records with errors coming out of here...
|
||||||
|
|
||||||
List<QRecord> oldRecordList = getOldRecordListForAuditIfNeeded(updateInput);
|
|
||||||
|
|
||||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateInput.getBackend());
|
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateInput.getBackend());
|
||||||
|
UpdateInterface updateInterface = qModule.getUpdateInterface();
|
||||||
|
|
||||||
|
List<QRecord> oldRecordList = updateInterface.supportsPreFetchQuery() ? getOldRecordListForAuditIfNeeded(updateInput) : new ArrayList<>();
|
||||||
|
|
||||||
validatePrimaryKeysAreGiven(updateInput);
|
validatePrimaryKeysAreGiven(updateInput);
|
||||||
validateRecordsExistAndCanBeAccessed(updateInput, oldRecordList);
|
|
||||||
|
if(updateInterface.supportsPreFetchQuery())
|
||||||
|
{
|
||||||
|
validateRecordsExistAndCanBeAccessed(updateInput, oldRecordList);
|
||||||
|
}
|
||||||
|
|
||||||
validateRequiredFields(updateInput);
|
validateRequiredFields(updateInput);
|
||||||
ValidateRecordSecurityLockHelper.validateSecurityFields(updateInput.getTable(), updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
|
ValidateRecordSecurityLockHelper.validateSecurityFields(updateInput.getTable(), updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
|
||||||
|
|
||||||
// todo pre-customization - just get to modify the request?
|
// todo pre-customization - just get to modify the request?
|
||||||
UpdateOutput updateOutput = qModule.getUpdateInterface().execute(updateInput);
|
UpdateOutput updateOutput = updateInterface.execute(updateInput);
|
||||||
// todo post-customization - can do whatever w/ the result if you want
|
// todo post-customization - can do whatever w/ the result if you want
|
||||||
|
|
||||||
List<String> errors = updateOutput.getRecords().stream().flatMap(r -> r.getErrors().stream()).toList();
|
List<String> errors = updateOutput.getRecords().stream().flatMap(r -> r.getErrors().stream()).toList();
|
||||||
|
@ -261,7 +261,7 @@ public class ValidateRecordSecurityLockHelper
|
|||||||
QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
|
QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
|
||||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && QContext.getQSession().hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
|
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && QContext.getQSession().hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
|
||||||
{
|
{
|
||||||
LOG.debug("Session has " + securityKeyType.getAllAccessKeyName() + " - not checking this lock.");
|
LOG.trace("Session has " + securityKeyType.getAllAccessKeyName() + " - not checking this lock.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -31,6 +31,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFieldMapping;
|
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFieldMapping;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
@ -61,7 +62,7 @@ public class CsvToQRecordAdapter
|
|||||||
** using a given mapping.
|
** using a given mapping.
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void buildRecordsFromCsv(RecordPipe recordPipe, String csv, QTableMetaData table, AbstractQFieldMapping<?> mapping, Consumer<QRecord> recordCustomizer)
|
public void buildRecordsFromCsv(RecordPipe recordPipe, String csv, QTableMetaData table, AbstractQFieldMapping<?> mapping, Consumer<QRecord> recordCustomizer) throws QException
|
||||||
{
|
{
|
||||||
buildRecordsFromCsv(new InputWrapper().withRecordPipe(recordPipe).withCsv(csv).withTable(table).withMapping(mapping).withRecordCustomizer(recordCustomizer));
|
buildRecordsFromCsv(new InputWrapper().withRecordPipe(recordPipe).withCsv(csv).withTable(table).withMapping(mapping).withRecordCustomizer(recordCustomizer));
|
||||||
}
|
}
|
||||||
@ -73,7 +74,7 @@ public class CsvToQRecordAdapter
|
|||||||
** using a given mapping.
|
** using a given mapping.
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public List<QRecord> buildRecordsFromCsv(String csv, QTableMetaData table, AbstractQFieldMapping<?> mapping)
|
public List<QRecord> buildRecordsFromCsv(String csv, QTableMetaData table, AbstractQFieldMapping<?> mapping) throws QException
|
||||||
{
|
{
|
||||||
buildRecordsFromCsv(new InputWrapper().withCsv(csv).withTable(table).withMapping(mapping));
|
buildRecordsFromCsv(new InputWrapper().withCsv(csv).withTable(table).withMapping(mapping));
|
||||||
return (recordList);
|
return (recordList);
|
||||||
@ -87,7 +88,7 @@ public class CsvToQRecordAdapter
|
|||||||
**
|
**
|
||||||
** todo - meta-data validation, type handling
|
** todo - meta-data validation, type handling
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void buildRecordsFromCsv(InputWrapper inputWrapper)
|
public void buildRecordsFromCsv(InputWrapper inputWrapper) throws QException
|
||||||
{
|
{
|
||||||
String csv = inputWrapper.getCsv();
|
String csv = inputWrapper.getCsv();
|
||||||
AbstractQFieldMapping<?> mapping = inputWrapper.getMapping();
|
AbstractQFieldMapping<?> mapping = inputWrapper.getMapping();
|
||||||
@ -297,7 +298,7 @@ public class CsvToQRecordAdapter
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Add a record - either to the pipe, or list, whichever we're building.
|
** Add a record - either to the pipe, or list, whichever we're building.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void addRecord(QRecord record)
|
private void addRecord(QRecord record) throws QException
|
||||||
{
|
{
|
||||||
if(recordPipe != null)
|
if(recordPipe != null)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.actions.scripts;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Base class for input wrappers that end up running scripts (ExecuteCodeAction)
|
||||||
|
*******************************************************************************/
|
||||||
|
public class AbstractRunScriptInput<C extends QCodeReference> extends AbstractTableActionInput
|
||||||
|
{
|
||||||
|
private C codeReference;
|
||||||
|
private Map<String, Serializable> inputValues;
|
||||||
|
private QCodeExecutionLoggerInterface logger;
|
||||||
|
private Serializable outputObject;
|
||||||
|
private Serializable scriptUtils;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for codeReference
|
||||||
|
*******************************************************************************/
|
||||||
|
public C getCodeReference()
|
||||||
|
{
|
||||||
|
return (this.codeReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for codeReference
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setCodeReference(C codeReference)
|
||||||
|
{
|
||||||
|
this.codeReference = codeReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for codeReference
|
||||||
|
*******************************************************************************/
|
||||||
|
public AbstractRunScriptInput<C> withCodeReference(C codeReference)
|
||||||
|
{
|
||||||
|
this.codeReference = codeReference;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for inputValues
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, Serializable> getInputValues()
|
||||||
|
{
|
||||||
|
return (this.inputValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for inputValues
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setInputValues(Map<String, Serializable> inputValues)
|
||||||
|
{
|
||||||
|
this.inputValues = inputValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for inputValues
|
||||||
|
*******************************************************************************/
|
||||||
|
public AbstractRunScriptInput<C> withInputValues(Map<String, Serializable> inputValues)
|
||||||
|
{
|
||||||
|
this.inputValues = inputValues;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for logger
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCodeExecutionLoggerInterface getLogger()
|
||||||
|
{
|
||||||
|
return (this.logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for logger
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setLogger(QCodeExecutionLoggerInterface logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for logger
|
||||||
|
*******************************************************************************/
|
||||||
|
public AbstractRunScriptInput<C> withLogger(QCodeExecutionLoggerInterface logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for outputObject
|
||||||
|
*******************************************************************************/
|
||||||
|
public Serializable getOutputObject()
|
||||||
|
{
|
||||||
|
return (this.outputObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for outputObject
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setOutputObject(Serializable outputObject)
|
||||||
|
{
|
||||||
|
this.outputObject = outputObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for outputObject
|
||||||
|
*******************************************************************************/
|
||||||
|
public AbstractRunScriptInput<C> withOutputObject(Serializable outputObject)
|
||||||
|
{
|
||||||
|
this.outputObject = outputObject;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for scriptUtils
|
||||||
|
*******************************************************************************/
|
||||||
|
public Serializable getScriptUtils()
|
||||||
|
{
|
||||||
|
return (this.scriptUtils);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for scriptUtils
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setScriptUtils(Serializable scriptUtils)
|
||||||
|
{
|
||||||
|
this.scriptUtils = scriptUtils;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for scriptUtils
|
||||||
|
*******************************************************************************/
|
||||||
|
public AbstractRunScriptInput<C> withScriptUtils(Serializable scriptUtils)
|
||||||
|
{
|
||||||
|
this.scriptUtils = scriptUtils;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -24,9 +24,6 @@ package com.kingsrook.qqq.backend.core.model.actions.scripts;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference;
|
||||||
|
|
||||||
@ -34,18 +31,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReferen
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class RunAdHocRecordScriptInput extends AbstractTableActionInput
|
public class RunAdHocRecordScriptInput extends AbstractRunScriptInput<AdHocScriptCodeReference>
|
||||||
{
|
{
|
||||||
private AdHocScriptCodeReference codeReference;
|
private List<Serializable> recordPrimaryKeyList; // can either supply recordList, or recordPrimaryKeyList
|
||||||
private Map<String, Serializable> inputValues;
|
private List<QRecord> recordList;
|
||||||
private List<Serializable> recordPrimaryKeyList; // can either supply recordList, or recordPrimaryKeyList
|
|
||||||
private List<QRecord> recordList;
|
|
||||||
private String tableName;
|
|
||||||
private QCodeExecutionLoggerInterface logger;
|
|
||||||
|
|
||||||
private Serializable outputObject;
|
|
||||||
|
|
||||||
private Serializable scriptUtils;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -58,189 +47,6 @@ public class RunAdHocRecordScriptInput extends AbstractTableActionInput
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for inputValues
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public Map<String, Serializable> getInputValues()
|
|
||||||
{
|
|
||||||
return inputValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for inputValues
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setInputValues(Map<String, Serializable> inputValues)
|
|
||||||
{
|
|
||||||
this.inputValues = inputValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for inputValues
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public RunAdHocRecordScriptInput withInputValues(Map<String, Serializable> inputValues)
|
|
||||||
{
|
|
||||||
this.inputValues = inputValues;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for outputObject
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public Serializable getOutputObject()
|
|
||||||
{
|
|
||||||
return outputObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for outputObject
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setOutputObject(Serializable outputObject)
|
|
||||||
{
|
|
||||||
this.outputObject = outputObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for outputObject
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public RunAdHocRecordScriptInput withOutputObject(Serializable outputObject)
|
|
||||||
{
|
|
||||||
this.outputObject = outputObject;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for logger
|
|
||||||
*******************************************************************************/
|
|
||||||
public QCodeExecutionLoggerInterface getLogger()
|
|
||||||
{
|
|
||||||
return (this.logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for logger
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setLogger(QCodeExecutionLoggerInterface logger)
|
|
||||||
{
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for logger
|
|
||||||
*******************************************************************************/
|
|
||||||
public RunAdHocRecordScriptInput withLogger(QCodeExecutionLoggerInterface logger)
|
|
||||||
{
|
|
||||||
this.logger = logger;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for scriptUtils
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public Serializable getScriptUtils()
|
|
||||||
{
|
|
||||||
return scriptUtils;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for scriptUtils
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setScriptUtils(Serializable scriptUtils)
|
|
||||||
{
|
|
||||||
this.scriptUtils = scriptUtils;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for codeReference
|
|
||||||
*******************************************************************************/
|
|
||||||
public AdHocScriptCodeReference getCodeReference()
|
|
||||||
{
|
|
||||||
return (this.codeReference);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for codeReference
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setCodeReference(AdHocScriptCodeReference codeReference)
|
|
||||||
{
|
|
||||||
this.codeReference = codeReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for codeReference
|
|
||||||
*******************************************************************************/
|
|
||||||
public RunAdHocRecordScriptInput withCodeReference(AdHocScriptCodeReference codeReference)
|
|
||||||
{
|
|
||||||
this.codeReference = codeReference;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for tableName
|
|
||||||
*******************************************************************************/
|
|
||||||
public String getTableName()
|
|
||||||
{
|
|
||||||
return (this.tableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for tableName
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setTableName(String tableName)
|
|
||||||
{
|
|
||||||
this.tableName = tableName;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for tableName
|
|
||||||
*******************************************************************************/
|
|
||||||
public RunAdHocRecordScriptInput withTableName(String tableName)
|
|
||||||
{
|
|
||||||
this.tableName = tableName;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for recordList
|
** Getter for recordList
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -22,187 +22,46 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.actions.scripts;
|
package com.kingsrook.qqq.backend.core.model.actions.scripts;
|
||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import com.kingsrook.qqq.backend.core.actions.scripts.AssociatedScriptContextPrimerInterface;
|
||||||
import java.util.Map;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class RunAssociatedScriptInput extends AbstractTableActionInput
|
public class RunAssociatedScriptInput extends AbstractRunScriptInput<AssociatedScriptCodeReference>
|
||||||
{
|
{
|
||||||
private AssociatedScriptCodeReference codeReference;
|
private AssociatedScriptContextPrimerInterface associatedScriptContextPrimerInterface;
|
||||||
private Map<String, Serializable> inputValues;
|
|
||||||
private QCodeExecutionLoggerInterface logger;
|
|
||||||
|
|
||||||
private Serializable outputObject;
|
|
||||||
|
|
||||||
private Serializable scriptUtils;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
** Getter for associatedScriptContextPrimerInterface
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public RunAssociatedScriptInput()
|
public AssociatedScriptContextPrimerInterface getAssociatedScriptContextPrimerInterface()
|
||||||
{
|
{
|
||||||
|
return (this.associatedScriptContextPrimerInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for codeReference
|
** Setter for associatedScriptContextPrimerInterface
|
||||||
**
|
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public AssociatedScriptCodeReference getCodeReference()
|
public void setAssociatedScriptContextPrimerInterface(AssociatedScriptContextPrimerInterface associatedScriptContextPrimerInterface)
|
||||||
{
|
{
|
||||||
return codeReference;
|
this.associatedScriptContextPrimerInterface = associatedScriptContextPrimerInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Setter for codeReference
|
** Fluent setter for associatedScriptContextPrimerInterface
|
||||||
**
|
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void setCodeReference(AssociatedScriptCodeReference codeReference)
|
public RunAssociatedScriptInput withAssociatedScriptContextPrimerInterface(AssociatedScriptContextPrimerInterface associatedScriptContextPrimerInterface)
|
||||||
{
|
{
|
||||||
this.codeReference = codeReference;
|
this.associatedScriptContextPrimerInterface = associatedScriptContextPrimerInterface;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for codeReference
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public RunAssociatedScriptInput withCodeReference(AssociatedScriptCodeReference codeReference)
|
|
||||||
{
|
|
||||||
this.codeReference = codeReference;
|
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for inputValues
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public Map<String, Serializable> getInputValues()
|
|
||||||
{
|
|
||||||
return inputValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for inputValues
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setInputValues(Map<String, Serializable> inputValues)
|
|
||||||
{
|
|
||||||
this.inputValues = inputValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for inputValues
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public RunAssociatedScriptInput withInputValues(Map<String, Serializable> inputValues)
|
|
||||||
{
|
|
||||||
this.inputValues = inputValues;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for outputObject
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public Serializable getOutputObject()
|
|
||||||
{
|
|
||||||
return outputObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for outputObject
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setOutputObject(Serializable outputObject)
|
|
||||||
{
|
|
||||||
this.outputObject = outputObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for outputObject
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public RunAssociatedScriptInput withOutputObject(Serializable outputObject)
|
|
||||||
{
|
|
||||||
this.outputObject = outputObject;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for logger
|
|
||||||
*******************************************************************************/
|
|
||||||
public QCodeExecutionLoggerInterface getLogger()
|
|
||||||
{
|
|
||||||
return (this.logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for logger
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setLogger(QCodeExecutionLoggerInterface logger)
|
|
||||||
{
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for logger
|
|
||||||
*******************************************************************************/
|
|
||||||
public RunAssociatedScriptInput withLogger(QCodeExecutionLoggerInterface logger)
|
|
||||||
{
|
|
||||||
this.logger = logger;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for scriptUtils
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public Serializable getScriptUtils()
|
|
||||||
{
|
|
||||||
return scriptUtils;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for scriptUtils
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setScriptUtils(Serializable scriptUtils)
|
|
||||||
{
|
|
||||||
this.scriptUtils = scriptUtils;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,8 @@ public class StoreAssociatedScriptInput extends AbstractTableActionInput
|
|||||||
private Serializable recordPrimaryKey;
|
private Serializable recordPrimaryKey;
|
||||||
|
|
||||||
private String code;
|
private String code;
|
||||||
|
private String apiName;
|
||||||
|
private String apiVersion;
|
||||||
private String commitMessage;
|
private String commitMessage;
|
||||||
|
|
||||||
|
|
||||||
@ -183,4 +185,66 @@ public class StoreAssociatedScriptInput extends AbstractTableActionInput
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for apiName
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getApiName()
|
||||||
|
{
|
||||||
|
return (this.apiName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for apiName
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setApiName(String apiName)
|
||||||
|
{
|
||||||
|
this.apiName = apiName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for apiName
|
||||||
|
*******************************************************************************/
|
||||||
|
public StoreAssociatedScriptInput withApiName(String apiName)
|
||||||
|
{
|
||||||
|
this.apiName = apiName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for apiVersion
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getApiVersion()
|
||||||
|
{
|
||||||
|
return (this.apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for apiVersion
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setApiVersion(String apiVersion)
|
||||||
|
{
|
||||||
|
this.apiVersion = apiVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for apiVersion
|
||||||
|
*******************************************************************************/
|
||||||
|
public StoreAssociatedScriptInput withApiVersion(String apiVersion)
|
||||||
|
{
|
||||||
|
this.apiVersion = apiVersion;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,9 @@ public class TestScriptInput extends AbstractTableActionInput
|
|||||||
private Map<String, Serializable> inputValues;
|
private Map<String, Serializable> inputValues;
|
||||||
private QCodeReference codeReference;
|
private QCodeReference codeReference;
|
||||||
|
|
||||||
|
private String apiName;
|
||||||
|
private String apiVersion;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -113,4 +116,66 @@ public class TestScriptInput extends AbstractTableActionInput
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for apiName
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getApiName()
|
||||||
|
{
|
||||||
|
return (this.apiName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for apiName
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setApiName(String apiName)
|
||||||
|
{
|
||||||
|
this.apiName = apiName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for apiName
|
||||||
|
*******************************************************************************/
|
||||||
|
public TestScriptInput withApiName(String apiName)
|
||||||
|
{
|
||||||
|
this.apiName = apiName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for apiVersion
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getApiVersion()
|
||||||
|
{
|
||||||
|
return (this.apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for apiVersion
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setApiVersion(String apiVersion)
|
||||||
|
{
|
||||||
|
this.apiVersion = apiVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for apiVersion
|
||||||
|
*******************************************************************************/
|
||||||
|
public TestScriptInput withApiVersion(String apiVersion)
|
||||||
|
{
|
||||||
|
this.apiVersion = apiVersion;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
|
|||||||
** that could be read asynchronously, at any time, by another thread - SO - only
|
** that could be read asynchronously, at any time, by another thread - SO - only
|
||||||
** completely populated records should be passed into this method.
|
** completely populated records should be passed into this method.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void addRecord(QRecord record)
|
public void addRecord(QRecord record) throws QException
|
||||||
{
|
{
|
||||||
storage.addRecord(record);
|
storage.addRecord(record);
|
||||||
}
|
}
|
||||||
@ -73,7 +74,7 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** add a list of records to this output
|
** add a list of records to this output
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void addRecords(List<QRecord> records)
|
public void addRecords(List<QRecord> records) throws QException
|
||||||
{
|
{
|
||||||
storage.addRecords(records);
|
storage.addRecords(records);
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ class QueryOutputRecordPipe implements QueryOutputStorageInterface
|
|||||||
** add a record to this output
|
** add a record to this output
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
public void addRecord(QRecord record)
|
public void addRecord(QRecord record) throws QException
|
||||||
{
|
{
|
||||||
recordPipe.addRecord(record);
|
recordPipe.addRecord(record);
|
||||||
}
|
}
|
||||||
@ -64,7 +65,7 @@ class QueryOutputRecordPipe implements QueryOutputStorageInterface
|
|||||||
** add a list of records to this output
|
** add a list of records to this output
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
public void addRecords(List<QRecord> records)
|
public void addRecords(List<QRecord> records) throws QException
|
||||||
{
|
{
|
||||||
recordPipe.addRecords(records);
|
recordPipe.addRecords(records);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
|||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
|
||||||
|
|
||||||
@ -36,13 +37,13 @@ interface QueryOutputStorageInterface
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** add a records to this output
|
** add a records to this output
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
void addRecord(QRecord record);
|
void addRecord(QRecord record) throws QException;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** add a list of records to this output
|
** add a list of records to this output
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
void addRecords(List<QRecord> records);
|
void addRecords(List<QRecord> records) throws QException;
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Get all stored records
|
** Get all stored records
|
||||||
|
@ -38,6 +38,12 @@ public class TableAutomationAction
|
|||||||
private Integer priority = 500;
|
private Integer priority = 500;
|
||||||
private QQueryFilter filter;
|
private QQueryFilter filter;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// flag that will cause the records to cause their associations to be //
|
||||||
|
// fetched, when they are looked up for passing into the action //
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
private boolean includeRecordAssociations = false;
|
||||||
|
|
||||||
private Map<String, Serializable> values;
|
private Map<String, Serializable> values;
|
||||||
|
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
@ -292,4 +298,35 @@ public class TableAutomationAction
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for includeRecordAssociations
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean getIncludeRecordAssociations()
|
||||||
|
{
|
||||||
|
return (this.includeRecordAssociations);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for includeRecordAssociations
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setIncludeRecordAssociations(boolean includeRecordAssociations)
|
||||||
|
{
|
||||||
|
this.includeRecordAssociations = includeRecordAssociations;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for includeRecordAssociations
|
||||||
|
*******************************************************************************/
|
||||||
|
public TableAutomationAction withIncludeRecordAssociations(boolean includeRecordAssociations)
|
||||||
|
{
|
||||||
|
this.includeRecordAssociations = includeRecordAssociations;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|||||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -48,16 +49,22 @@ public class ScriptRevision extends QRecordEntity
|
|||||||
@QField(possibleValueSourceName = "script")
|
@QField(possibleValueSourceName = "script")
|
||||||
private Integer scriptId;
|
private Integer scriptId;
|
||||||
|
|
||||||
|
@QField(possibleValueSourceName = "apiVersion", label = "API Version")
|
||||||
|
private String apiVersion;
|
||||||
|
|
||||||
|
@QField(possibleValueSourceName = "apiName", label = "API Name")
|
||||||
|
private String apiName;
|
||||||
|
|
||||||
@QField()
|
@QField()
|
||||||
private String contents;
|
private String contents;
|
||||||
|
|
||||||
@QField()
|
@QField()
|
||||||
private Integer sequenceNo;
|
private Integer sequenceNo;
|
||||||
|
|
||||||
@QField()
|
@QField(maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
|
||||||
private String commitMessage;
|
private String commitMessage;
|
||||||
|
|
||||||
@QField()
|
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
|
||||||
private String author;
|
private String author;
|
||||||
|
|
||||||
|
|
||||||
@ -353,4 +360,66 @@ public class ScriptRevision extends QRecordEntity
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for apiVersion
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getApiVersion()
|
||||||
|
{
|
||||||
|
return (this.apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for apiVersion
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setApiVersion(String apiVersion)
|
||||||
|
{
|
||||||
|
this.apiVersion = apiVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for apiVersion
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScriptRevision withApiVersion(String apiVersion)
|
||||||
|
{
|
||||||
|
this.apiVersion = apiVersion;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for apiName
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getApiName()
|
||||||
|
{
|
||||||
|
return (this.apiName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for apiName
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setApiName(String apiName)
|
||||||
|
{
|
||||||
|
this.apiName = apiName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for apiName
|
||||||
|
*******************************************************************************/
|
||||||
|
public ScriptRevision withApiName(String apiName)
|
||||||
|
{
|
||||||
|
this.apiName = apiName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -389,6 +389,23 @@ public class ScriptsMetaDataProvider
|
|||||||
tableMetaData.getField("contents").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR).withValue(AdornmentType.CodeEditorValues.languageMode("javascript")));
|
tableMetaData.getField("contents").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR).withValue(AdornmentType.CodeEditorValues.languageMode("javascript")));
|
||||||
tableMetaData.getField("scriptId").withFieldAdornment(AdornmentType.Size.LARGE.toAdornment());
|
tableMetaData.getField("scriptId").withFieldAdornment(AdornmentType.Size.LARGE.toAdornment());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if the api module is loaded, then add a section to the table for the api name & version fields //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Class.forName("com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataProvider");
|
||||||
|
tableMetaData.getSections().add(1, new QFieldSection("api", "API", new QIcon().withName("code"), Tier.T2, List.of("apiName", "apiVersion")));
|
||||||
|
}
|
||||||
|
catch(ClassNotFoundException e)
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if the api module is not loaded, then make sure we don't have these fields in our scripts table //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
tableMetaData.getFields().remove("apiName");
|
||||||
|
tableMetaData.getFields().remove("apiVersion");
|
||||||
|
}
|
||||||
|
|
||||||
return (tableMetaData);
|
return (tableMetaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
|||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -43,7 +44,16 @@ public class MemoryQueryAction implements QueryInterface
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
QueryOutput queryOutput = new QueryOutput(queryInput);
|
QueryOutput queryOutput = new QueryOutput(queryInput);
|
||||||
queryOutput.addRecords(MemoryRecordStore.getInstance().query(queryInput));
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// add the records to the output one-by-one -- this more closely matches how "real" backends perform //
|
||||||
|
// and works better w/ pipes //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
for(QRecord qRecord : MemoryRecordStore.getInstance().query(queryInput))
|
||||||
|
{
|
||||||
|
queryOutput.addRecord(qRecord);
|
||||||
|
}
|
||||||
|
|
||||||
return (queryOutput);
|
return (queryOutput);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
|
@ -83,6 +83,9 @@ public class ExtractViaQueryStep extends AbstractExtractStep
|
|||||||
queryInput.setRecordPipe(getRecordPipe());
|
queryInput.setRecordPipe(getRecordPipe());
|
||||||
queryInput.setLimit(getLimit());
|
queryInput.setLimit(getLimit());
|
||||||
queryInput.setAsyncJobCallback(runBackendStepInput.getAsyncJobCallback());
|
queryInput.setAsyncJobCallback(runBackendStepInput.getAsyncJobCallback());
|
||||||
|
|
||||||
|
customizeInputPreQuery(queryInput);
|
||||||
|
|
||||||
new QueryAction().execute(queryInput);
|
new QueryAction().execute(queryInput);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
@ -92,6 +95,16 @@ public class ExtractViaQueryStep extends AbstractExtractStep
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** chance for sub-classes to change things about the query input, if they want.
|
||||||
|
*******************************************************************************/
|
||||||
|
protected void customizeInputPreQuery(QueryInput queryInput)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -28,6 +28,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInpu
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
@ -70,4 +71,16 @@ public class RunRecordScriptExtractStep extends ExtractViaQueryStep
|
|||||||
super.preRun(runBackendStepInput, runBackendStepOutput);
|
super.preRun(runBackendStepInput, runBackendStepOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Make sure associations are fetched (so api records have children!)
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
protected void customizeInputPreQuery(QueryInput queryInput)
|
||||||
|
{
|
||||||
|
super.customizeInputPreQuery(queryInput);
|
||||||
|
queryInput.setIncludeAssociations(true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,8 @@ public class StoreScriptRevisionProcessStep implements BackendStep
|
|||||||
QRecord scriptRevision = new QRecord()
|
QRecord scriptRevision = new QRecord()
|
||||||
.withValue("scriptId", script.getValue("id"))
|
.withValue("scriptId", script.getValue("id"))
|
||||||
.withValue("contents", input.getValueString("contents"))
|
.withValue("contents", input.getValueString("contents"))
|
||||||
|
.withValue("apiName", input.getValueString("apiName"))
|
||||||
|
.withValue("apiVersion", input.getValueString("apiVersion"))
|
||||||
.withValue("commitMessage", commitMessage)
|
.withValue("commitMessage", commitMessage)
|
||||||
.withValue("sequenceNo", nextSequenceNo);
|
.withValue("sequenceNo", nextSequenceNo);
|
||||||
|
|
||||||
|
@ -74,11 +74,12 @@ public class TestScriptProcessStep implements BackendStep
|
|||||||
// get inputs //
|
// get inputs //
|
||||||
////////////////
|
////////////////
|
||||||
Integer scriptId = input.getValueInteger("scriptId");
|
Integer scriptId = input.getValueInteger("scriptId");
|
||||||
String code = input.getValueString("code");
|
|
||||||
|
|
||||||
ScriptRevision scriptRevision = new ScriptRevision();
|
ScriptRevision scriptRevision = new ScriptRevision();
|
||||||
scriptRevision.setScriptId(scriptId);
|
scriptRevision.setScriptId(scriptId);
|
||||||
scriptRevision.setContents(code);
|
scriptRevision.setContents(input.getValueString("code"));
|
||||||
|
scriptRevision.setApiName(input.getValueString("apiName"));
|
||||||
|
scriptRevision.setApiVersion(input.getValueString("apiVersion"));
|
||||||
|
|
||||||
BuildScriptLogAndScriptLogLineExecutionLogger executionLogger = new BuildScriptLogAndScriptLogLineExecutionLogger(null, null);
|
BuildScriptLogAndScriptLogLineExecutionLogger executionLogger = new BuildScriptLogAndScriptLogLineExecutionLogger(null, null);
|
||||||
|
|
||||||
@ -106,6 +107,7 @@ public class TestScriptProcessStep implements BackendStep
|
|||||||
QueryInput queryInput = new QueryInput();
|
QueryInput queryInput = new QueryInput();
|
||||||
queryInput.setTableName(tableName);
|
queryInput.setTableName(tableName);
|
||||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, recordPrimaryKeyList.split(","))));
|
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, recordPrimaryKeyList.split(","))));
|
||||||
|
queryInput.setIncludeAssociations(true);
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
if(CollectionUtils.nullSafeIsEmpty(queryOutput.getRecords()))
|
if(CollectionUtils.nullSafeIsEmpty(queryOutput.getRecords()))
|
||||||
{
|
{
|
||||||
|
@ -26,6 +26,7 @@ import java.io.Serializable;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
@ -189,6 +190,77 @@ class QueryActionTest extends BaseTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testQueryAssociationsWithPipe() throws QException
|
||||||
|
{
|
||||||
|
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
|
||||||
|
insert2OrdersWith3Lines3LineExtrinsicsAnd4OrderExtrinsicAssociations();
|
||||||
|
|
||||||
|
RecordPipe pipe = new RecordPipe();
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
queryInput.setRecordPipe(pipe);
|
||||||
|
queryInput.setIncludeAssociations(true);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertNotNull(queryOutput);
|
||||||
|
|
||||||
|
List<QRecord> records = pipe.consumeAvailableRecords();
|
||||||
|
assertThat(records).isNotEmpty();
|
||||||
|
|
||||||
|
QRecord order0 = records.get(0);
|
||||||
|
assertEquals(2, order0.getAssociatedRecords().get("orderLine").size());
|
||||||
|
assertEquals(3, order0.getAssociatedRecords().get("extrinsics").size());
|
||||||
|
|
||||||
|
QRecord orderLine00 = order0.getAssociatedRecords().get("orderLine").get(0);
|
||||||
|
assertEquals(1, orderLine00.getAssociatedRecords().get("extrinsics").size());
|
||||||
|
QRecord orderLine01 = order0.getAssociatedRecords().get("orderLine").get(1);
|
||||||
|
assertEquals(2, orderLine01.getAssociatedRecords().get("extrinsics").size());
|
||||||
|
|
||||||
|
QRecord order1 = records.get(1);
|
||||||
|
assertEquals(1, order1.getAssociatedRecords().get("orderLine").size());
|
||||||
|
assertEquals(1, order1.getAssociatedRecords().get("extrinsics").size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testQueryManyRecordsAssociationsWithPipe() throws QException
|
||||||
|
{
|
||||||
|
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
|
||||||
|
insertNOrdersWithAssociations(2500);
|
||||||
|
|
||||||
|
RecordPipe pipe = new RecordPipe(1000);
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
queryInput.setRecordPipe(pipe);
|
||||||
|
queryInput.setIncludeAssociations(true);
|
||||||
|
|
||||||
|
int recordsConsumed = new AsyncRecordPipeLoop().run("Test", null, pipe, (callback) ->
|
||||||
|
{
|
||||||
|
new QueryAction().execute(queryInput);
|
||||||
|
return (true);
|
||||||
|
}, () ->
|
||||||
|
{
|
||||||
|
List<QRecord> records = pipe.consumeAvailableRecords();
|
||||||
|
for(QRecord record : records)
|
||||||
|
{
|
||||||
|
assertEquals(1, record.getAssociatedRecords().get("orderLine").size());
|
||||||
|
assertEquals(1, record.getAssociatedRecords().get("extrinsics").size());
|
||||||
|
}
|
||||||
|
return (records.size());
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(2500, recordsConsumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -345,4 +417,25 @@ class QueryActionTest extends BaseTest
|
|||||||
));
|
));
|
||||||
new InsertAction().execute(insertInput);
|
new InsertAction().execute(insertInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static void insertNOrdersWithAssociations(int n) throws QException
|
||||||
|
{
|
||||||
|
List<QRecord> recordList = new ArrayList<>();
|
||||||
|
for(int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
recordList.add(new QRecord().withValue("storeId", 1).withValue("orderNo", "ORD" + i)
|
||||||
|
.withAssociatedRecord("orderLine", new QRecord().withValue("sku", "BASIC1").withValue("quantity", 3))
|
||||||
|
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "YOUR-FIELD").withValue("value", "YOUR-VALUE")));
|
||||||
|
}
|
||||||
|
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
insertInput.setRecords(recordList);
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.adapters;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.QIndexBasedFieldMapping;
|
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.QIndexBasedFieldMapping;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.QKeyBasedFieldMapping;
|
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.QKeyBasedFieldMapping;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
@ -48,7 +49,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
public void test_buildRecordsFromCsv_nullInput()
|
public void test_buildRecordsFromCsv_nullInput() throws QException
|
||||||
{
|
{
|
||||||
testExpectedToThrow(null);
|
testExpectedToThrow(null);
|
||||||
}
|
}
|
||||||
@ -59,7 +60,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
public void test_buildRecordsFromCsv_emptyStringInput()
|
public void test_buildRecordsFromCsv_emptyStringInput() throws QException
|
||||||
{
|
{
|
||||||
testExpectedToThrow("");
|
testExpectedToThrow("");
|
||||||
}
|
}
|
||||||
@ -69,7 +70,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void testExpectedToThrow(String csv)
|
private void testExpectedToThrow(String csv) throws QException
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -92,7 +93,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
public void test_buildRecordsFromCsv_emptyList()
|
public void test_buildRecordsFromCsv_emptyList() throws QException
|
||||||
{
|
{
|
||||||
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
||||||
List<QRecord> qRecords = csvToQRecordAdapter.buildRecordsFromCsv(getPersonCsvHeader(), TestUtils.defineTablePerson(), null);
|
List<QRecord> qRecords = csvToQRecordAdapter.buildRecordsFromCsv(getPersonCsvHeader(), TestUtils.defineTablePerson(), null);
|
||||||
@ -142,7 +143,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
public void test_buildRecordsFromCsv_oneRowStandardHeaderNoMapping()
|
public void test_buildRecordsFromCsv_oneRowStandardHeaderNoMapping() throws QException
|
||||||
{
|
{
|
||||||
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
||||||
List<QRecord> qRecords = csvToQRecordAdapter.buildRecordsFromCsv(getPersonCsvHeader() + getPersonCsvRow1(), TestUtils.defineTablePerson(), null);
|
List<QRecord> qRecords = csvToQRecordAdapter.buildRecordsFromCsv(getPersonCsvHeader() + getPersonCsvRow1(), TestUtils.defineTablePerson(), null);
|
||||||
@ -159,7 +160,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
public void test_buildRecordsFromCsv_twoRowsStandardHeaderNoMapping()
|
public void test_buildRecordsFromCsv_twoRowsStandardHeaderNoMapping() throws QException
|
||||||
{
|
{
|
||||||
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
||||||
List<QRecord> qRecords = csvToQRecordAdapter.buildRecordsFromCsv(getPersonCsvHeader() + getPersonCsvRow1() + getPersonCsvRow2(), TestUtils.defineTablePerson(), null);
|
List<QRecord> qRecords = csvToQRecordAdapter.buildRecordsFromCsv(getPersonCsvHeader() + getPersonCsvRow1() + getPersonCsvRow2(), TestUtils.defineTablePerson(), null);
|
||||||
@ -179,7 +180,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
public void test_buildRecordsFromCsv_oneRowCustomKeyBasedMapping()
|
public void test_buildRecordsFromCsv_oneRowCustomKeyBasedMapping() throws QException
|
||||||
{
|
{
|
||||||
String csvCustomHeader = """
|
String csvCustomHeader = """
|
||||||
"id","created","modified","first","last","birthday","email"\r
|
"id","created","modified","first","last","birthday","email"\r
|
||||||
@ -209,7 +210,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
public void test_buildRecordsFromCsv_twoRowsCustomIndexBasedMapping()
|
public void test_buildRecordsFromCsv_twoRowsCustomIndexBasedMapping() throws QException
|
||||||
{
|
{
|
||||||
int index = 1;
|
int index = 1;
|
||||||
QIndexBasedFieldMapping mapping = new QIndexBasedFieldMapping()
|
QIndexBasedFieldMapping mapping = new QIndexBasedFieldMapping()
|
||||||
@ -241,7 +242,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
** header names on the RHS.
|
** header names on the RHS.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
public void test_duplicatedColumnHeaders()
|
public void test_duplicatedColumnHeaders() throws QException
|
||||||
{
|
{
|
||||||
QKeyBasedFieldMapping mapping = new QKeyBasedFieldMapping()
|
QKeyBasedFieldMapping mapping = new QKeyBasedFieldMapping()
|
||||||
.withMapping("id", "id")
|
.withMapping("id", "id")
|
||||||
@ -291,7 +292,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
void testByteOrderMarker()
|
void testByteOrderMarker() throws QException
|
||||||
{
|
{
|
||||||
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
||||||
|
|
||||||
@ -313,7 +314,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
** Fix an IndexOutOfBounds that we used to throw.
|
** Fix an IndexOutOfBounds that we used to throw.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
void testTooFewBodyColumns()
|
void testTooFewBodyColumns() throws QException
|
||||||
{
|
{
|
||||||
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
||||||
List<QRecord> records = csvToQRecordAdapter.buildRecordsFromCsv("""
|
List<QRecord> records = csvToQRecordAdapter.buildRecordsFromCsv("""
|
||||||
@ -331,7 +332,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
public void testTooFewColumnsIndexMapping()
|
public void testTooFewColumnsIndexMapping() throws QException
|
||||||
{
|
{
|
||||||
int index = 1;
|
int index = 1;
|
||||||
QIndexBasedFieldMapping mapping = new QIndexBasedFieldMapping()
|
QIndexBasedFieldMapping mapping = new QIndexBasedFieldMapping()
|
||||||
@ -353,7 +354,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
void testCaseSensitiveHeaders()
|
void testCaseSensitiveHeaders() throws QException
|
||||||
{
|
{
|
||||||
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
||||||
csvToQRecordAdapter.buildRecordsFromCsv(new CsvToQRecordAdapter.InputWrapper()
|
csvToQRecordAdapter.buildRecordsFromCsv(new CsvToQRecordAdapter.InputWrapper()
|
||||||
@ -376,7 +377,7 @@ class CsvToQRecordAdapterTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
void testCaseInsensitiveHeaders()
|
void testCaseInsensitiveHeaders() throws QException
|
||||||
{
|
{
|
||||||
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
CsvToQRecordAdapter csvToQRecordAdapter = new CsvToQRecordAdapter();
|
||||||
csvToQRecordAdapter.buildRecordsFromCsv(new CsvToQRecordAdapter.InputWrapper()
|
csvToQRecordAdapter.buildRecordsFromCsv(new CsvToQRecordAdapter.InputWrapper()
|
||||||
|
@ -222,7 +222,10 @@ class TableSyncProcessTest extends BaseTest
|
|||||||
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||||
{
|
{
|
||||||
List<QRecord> qRecords = TestUtils.queryTable(QContext.getQInstance(), TestUtils.TABLE_NAME_PERSON_MEMORY);
|
List<QRecord> qRecords = TestUtils.queryTable(QContext.getQInstance(), TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||||
qRecords.forEach(r -> getRecordPipe().addRecord(r));
|
for(QRecord qRecord : qRecords)
|
||||||
|
{
|
||||||
|
getRecordPipe().addRecord(qRecord);
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
// re-add records 1 and 5 to the pipe //
|
// re-add records 1 and 5 to the pipe //
|
||||||
|
@ -34,10 +34,6 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<!-- props specifically to this module -->
|
<!-- props specifically to this module -->
|
||||||
<!-- none at this time -->
|
<!-- none at this time -->
|
||||||
|
|
||||||
<!-- todo - remove this once module is further built out and we can hit standard ratio -->
|
|
||||||
<coverage.instructionCoveredRatioMinimum>0.00</coverage.instructionCoveredRatioMinimum>
|
|
||||||
<coverage.classCoveredRatioMinimum>0.00</coverage.classCoveredRatioMinimum>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -45,4 +45,16 @@ public class APIUpdateAction extends AbstractAPIAction implements UpdateInterfac
|
|||||||
return (apiActionUtil.doUpdate(table, updateInput));
|
return (apiActionUtil.doUpdate(table, updateInput));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Specify whether this particular module's update action can & should fetch
|
||||||
|
** records before updating them, e.g., for audits or "not-found-checks"
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public boolean supportsPreFetchQuery()
|
||||||
|
{
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -634,7 +634,7 @@ public class BaseAPIActionUtil
|
|||||||
request.setEntity(new StringEntity(postBody));
|
request.setEntity(new StringEntity(postBody));
|
||||||
request.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
|
request.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
|
||||||
|
|
||||||
HttpResponse response = client.execute(request);
|
HttpResponse response = executeOAuthTokenRequest(client, request);
|
||||||
int statusCode = response.getStatusLine().getStatusCode();
|
int statusCode = response.getStatusLine().getStatusCode();
|
||||||
HttpEntity entity = response.getEntity();
|
HttpEntity entity = response.getEntity();
|
||||||
String resultString = EntityUtils.toString(entity);
|
String resultString = EntityUtils.toString(entity);
|
||||||
@ -669,6 +669,16 @@ public class BaseAPIActionUtil
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** one-line method, factored out so mock/tests can override
|
||||||
|
*******************************************************************************/
|
||||||
|
protected CloseableHttpResponse executeOAuthTokenRequest(CloseableHttpClient client, HttpPost request) throws IOException
|
||||||
|
{
|
||||||
|
return client.execute(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** As part of making a request - set up its content-type header.
|
** As part of making a request - set up its content-type header.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -880,7 +890,7 @@ public class BaseAPIActionUtil
|
|||||||
LOG.info("POST contents [" + ((HttpPost) request).getEntity().toString() + "]");
|
LOG.info("POST contents [" + ((HttpPost) request).getEntity().toString() + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
try(CloseableHttpResponse response = httpClient.execute(request))
|
try(CloseableHttpResponse response = executeHttpRequest(request, httpClient))
|
||||||
{
|
{
|
||||||
QHttpResponse qResponse = new QHttpResponse(response);
|
QHttpResponse qResponse = new QHttpResponse(response);
|
||||||
|
|
||||||
@ -924,7 +934,7 @@ public class BaseAPIActionUtil
|
|||||||
rateLimitsCaught++;
|
rateLimitsCaught++;
|
||||||
if(rateLimitsCaught > getMaxAllowedRateLimitErrors())
|
if(rateLimitsCaught > getMaxAllowedRateLimitErrors())
|
||||||
{
|
{
|
||||||
LOG.error("Giving up POST to [" + table.getName() + "] after too many rate-limit errors (" + getMaxAllowedRateLimitErrors() + ")");
|
LOG.error("Giving up " + request.getMethod() + " to [" + table.getName() + "] after too many rate-limit errors (" + getMaxAllowedRateLimitErrors() + ")");
|
||||||
throw (new QException(rle));
|
throw (new QException(rle));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -950,6 +960,16 @@ public class BaseAPIActionUtil
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** one-line method, factored out so mock/tests can override
|
||||||
|
*******************************************************************************/
|
||||||
|
protected CloseableHttpResponse executeHttpRequest(HttpRequestBase request, CloseableHttpClient httpClient) throws IOException
|
||||||
|
{
|
||||||
|
return httpClient.execute(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -32,6 +32,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
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.fields.QFieldType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.mocks.MockApiActionUtils;
|
||||||
import com.kingsrook.qqq.backend.module.api.model.AuthorizationType;
|
import com.kingsrook.qqq.backend.module.api.model.AuthorizationType;
|
||||||
import com.kingsrook.qqq.backend.module.api.model.metadata.APIBackendMetaData;
|
import com.kingsrook.qqq.backend.module.api.model.metadata.APIBackendMetaData;
|
||||||
import com.kingsrook.qqq.backend.module.api.model.metadata.APITableBackendDetails;
|
import com.kingsrook.qqq.backend.module.api.model.metadata.APITableBackendDetails;
|
||||||
@ -42,7 +44,10 @@ import com.kingsrook.qqq.backend.module.api.model.metadata.APITableBackendDetail
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class TestUtils
|
public class TestUtils
|
||||||
{
|
{
|
||||||
|
public static final String MEMORY_BACKEND_NAME = "memory";
|
||||||
public static final String EASYPOST_BACKEND_NAME = "easypost";
|
public static final String EASYPOST_BACKEND_NAME = "easypost";
|
||||||
|
public static final String MOCK_BACKEND_NAME = "mock";
|
||||||
|
public static final String MOCK_TABLE_NAME = "mock";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -52,14 +57,69 @@ public class TestUtils
|
|||||||
public static QInstance defineInstance()
|
public static QInstance defineInstance()
|
||||||
{
|
{
|
||||||
QInstance qInstance = new QInstance();
|
QInstance qInstance = new QInstance();
|
||||||
qInstance.addBackend(defineBackend());
|
|
||||||
qInstance.addTable(defineTableEasypostTracker());
|
|
||||||
qInstance.setAuthentication(defineAuthentication());
|
qInstance.setAuthentication(defineAuthentication());
|
||||||
|
|
||||||
|
qInstance.addBackend(defineMemoryBackend());
|
||||||
|
|
||||||
|
qInstance.addBackend(defineMockBackend());
|
||||||
|
qInstance.addTable(defineMockTable());
|
||||||
|
|
||||||
|
qInstance.addBackend(defineEasypostBackend());
|
||||||
|
qInstance.addTable(defineTableEasypostTracker());
|
||||||
|
|
||||||
return (qInstance);
|
return (qInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Define the in-memory backend used in standard tests
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QBackendMetaData defineMemoryBackend()
|
||||||
|
{
|
||||||
|
return new QBackendMetaData()
|
||||||
|
.withName(MEMORY_BACKEND_NAME)
|
||||||
|
.withBackendType(MemoryBackendModule.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QBackendMetaData defineMockBackend()
|
||||||
|
{
|
||||||
|
return (new APIBackendMetaData()
|
||||||
|
.withName(MOCK_BACKEND_NAME)
|
||||||
|
.withAuthorizationType(AuthorizationType.API_KEY_HEADER)
|
||||||
|
.withBaseUrl("http://localhost:9999/mock")
|
||||||
|
.withContentType("application/json")
|
||||||
|
.withActionUtil(new QCodeReference(MockApiActionUtils.class, QCodeUsage.CUSTOMIZER))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QTableMetaData defineMockTable()
|
||||||
|
{
|
||||||
|
return (new QTableMetaData()
|
||||||
|
.withName(MOCK_TABLE_NAME)
|
||||||
|
.withBackendName(MOCK_BACKEND_NAME)
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withBackendDetails(new APITableBackendDetails()
|
||||||
|
.withTablePath("mock")
|
||||||
|
.withTableWrapperObjectName("mocks")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Define the authentication used in standard tests - using 'mock' type.
|
** Define the authentication used in standard tests - using 'mock' type.
|
||||||
**
|
**
|
||||||
@ -76,7 +136,7 @@ public class TestUtils
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static QBackendMetaData defineBackend()
|
public static QBackendMetaData defineEasypostBackend()
|
||||||
{
|
{
|
||||||
String apiKey = new QMetaDataVariableInterpreter().interpret("${env.EASYPOST_API_KEY}");
|
String apiKey = new QMetaDataVariableInterpreter().interpret("${env.EASYPOST_API_KEY}");
|
||||||
|
|
||||||
|
@ -0,0 +1,685 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. 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.module.api.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.TestUtils;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.exceptions.RateLimitException;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.mocks.MockApiActionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.mocks.MockApiUtilsHelper;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.model.AuthorizationType;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.model.OutboundAPILog;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.model.OutboundAPILogMetaDataProvider;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.model.metadata.APIBackendMetaData;
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for com.kingsrook.qqq.backend.module.api.actions.BaseAPIActionUtil
|
||||||
|
*******************************************************************************/
|
||||||
|
class BaseAPIActionUtilTest extends BaseTest
|
||||||
|
{
|
||||||
|
private static MockApiUtilsHelper mockApiUtilsHelper = new MockApiUtilsHelper();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach()
|
||||||
|
{
|
||||||
|
mockApiUtilsHelper = new MockApiUtilsHelper();
|
||||||
|
mockApiUtilsHelper.setUseMock(true);
|
||||||
|
MockApiActionUtils.mockApiUtilsHelper = mockApiUtilsHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testCount() throws QException
|
||||||
|
{
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
[
|
||||||
|
{"id": 1, "name": "Homer"},
|
||||||
|
{"id": 2, "name": "Marge"},
|
||||||
|
{"id": 3, "name": "Bart"},
|
||||||
|
{"id": 4, "name": "Lisa"},
|
||||||
|
{"id": 5, "name": "Maggie"}
|
||||||
|
]
|
||||||
|
""");
|
||||||
|
|
||||||
|
CountInput countInput = new CountInput();
|
||||||
|
countInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
CountOutput countOutput = new CountAction().execute(countInput);
|
||||||
|
assertEquals(5, countOutput.getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testCountError() throws QException
|
||||||
|
{
|
||||||
|
////////////////////////////////////////
|
||||||
|
// avoid the fully mocked makeRequest //
|
||||||
|
////////////////////////////////////////
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||||
|
{"error": "Server error"}
|
||||||
|
"""));
|
||||||
|
|
||||||
|
CountInput countInput = new CountInput();
|
||||||
|
countInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
assertThatThrownBy(() -> new CountAction().execute(countInput)).hasRootCauseInstanceOf(Exception.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testGet() throws QException
|
||||||
|
{
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"id": 3, "name": "Bart"},
|
||||||
|
""");
|
||||||
|
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
getInput.setPrimaryKey(3);
|
||||||
|
GetOutput getOutput = new GetAction().execute(getInput);
|
||||||
|
assertEquals(3, getOutput.getRecord().getValueInteger("id"));
|
||||||
|
assertEquals("Bart", getOutput.getRecord().getValueString("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testGetByKey() throws QException
|
||||||
|
{
|
||||||
|
QContext.getQInstance().getTable(TestUtils.MOCK_TABLE_NAME).withUniqueKey(new UniqueKey("id"));
|
||||||
|
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"id": 3, "name": "Bart"},
|
||||||
|
""");
|
||||||
|
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
getInput.setUniqueKey(Map.of("id", 3));
|
||||||
|
GetOutput getOutput = new GetAction().execute(getInput);
|
||||||
|
assertEquals(3, getOutput.getRecord().getValueInteger("id"));
|
||||||
|
assertEquals("Bart", getOutput.getRecord().getValueString("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testQuery() throws QException
|
||||||
|
{
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
[
|
||||||
|
{"id": 1, "name": "Homer"},
|
||||||
|
{"id": 2, "name": "Marge"},
|
||||||
|
{"id": 3, "name": "Bart"},
|
||||||
|
{"id": 4, "name": "Lisa"},
|
||||||
|
{"id": 5, "name": "Maggie"}
|
||||||
|
]
|
||||||
|
""");
|
||||||
|
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(5, queryOutput.getRecords().size());
|
||||||
|
assertEquals(1, queryOutput.getRecords().get(0).getValueInteger("id"));
|
||||||
|
assertEquals("Homer", queryOutput.getRecords().get(0).getValueString("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testQueryObjectWrappingList() throws QException
|
||||||
|
{
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"mocks": [
|
||||||
|
{"id": 1, "name": "Homer"},
|
||||||
|
{"id": 2, "name": "Marge"},
|
||||||
|
{"id": 3, "name": "Bart"},
|
||||||
|
{"id": 4, "name": "Lisa"},
|
||||||
|
{"id": 5, "name": "Maggie"}
|
||||||
|
]}
|
||||||
|
""");
|
||||||
|
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(5, queryOutput.getRecords().size());
|
||||||
|
assertEquals(1, queryOutput.getRecords().get(0).getValueInteger("id"));
|
||||||
|
assertEquals("Homer", queryOutput.getRecords().get(0).getValueString("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testQueryObjectWrappingSingleObject() throws QException
|
||||||
|
{
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"mocks":
|
||||||
|
{"id": 1, "name": "Homer"}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size());
|
||||||
|
assertEquals(1, queryOutput.getRecords().get(0).getValueInteger("id"));
|
||||||
|
assertEquals("Homer", queryOutput.getRecords().get(0).getValueString("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testQueryPaginate() throws QException
|
||||||
|
{
|
||||||
|
String oneObject = """
|
||||||
|
{"id": 1, "name": "Homer"}
|
||||||
|
""";
|
||||||
|
StringBuilder response = new StringBuilder("[");
|
||||||
|
for(int i = 0; i < 19; i++)
|
||||||
|
{
|
||||||
|
response.append(oneObject).append(",");
|
||||||
|
}
|
||||||
|
response.append(oneObject);
|
||||||
|
response.append("]");
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(response.toString());
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(response.toString());
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(response.toString());
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("[]");
|
||||||
|
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(60, queryOutput.getRecords().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testQueryError() throws QException
|
||||||
|
{
|
||||||
|
////////////////////////////////////////
|
||||||
|
// avoid the fully mocked makeRequest //
|
||||||
|
////////////////////////////////////////
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||||
|
{"error": "Server error"}
|
||||||
|
"""));
|
||||||
|
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
assertThatThrownBy(() -> new QueryAction().execute(queryInput)).hasRootCauseInstanceOf(Exception.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testInsert() throws QException
|
||||||
|
{
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"id": 6}
|
||||||
|
""");
|
||||||
|
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withValue("name", "Milhouse")));
|
||||||
|
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||||
|
assertEquals(6, insertOutput.getRecords().get(0).getValueInteger("id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testInsertEmptyInputList() throws QException
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
insertInput.setRecords(List.of());
|
||||||
|
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testInsertError() throws QException
|
||||||
|
{
|
||||||
|
////////////////////////////////////////
|
||||||
|
// avoid the fully mocked makeRequest //
|
||||||
|
////////////////////////////////////////
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||||
|
{"error": "Server error"}
|
||||||
|
"""));
|
||||||
|
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withValue("name", "Milhouse")));
|
||||||
|
insertInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||||
|
assertTrue(CollectionUtils.nullSafeHasContents(insertOutput.getRecords().get(0).getErrors()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testUpdate() throws QException
|
||||||
|
{
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("");
|
||||||
|
|
||||||
|
mockApiUtilsHelper.setMockRequestAsserter(httpRequestBase ->
|
||||||
|
{
|
||||||
|
String requestBody = MockApiUtilsHelper.readRequestBody(httpRequestBase);
|
||||||
|
JSONObject requestObject = new JSONObject(requestBody);
|
||||||
|
|
||||||
|
JSONArray mocks = requestObject.getJSONArray("mocks");
|
||||||
|
JSONObject record = mocks.getJSONObject(0);
|
||||||
|
|
||||||
|
assertEquals("Bartholomew", record.getString("name"));
|
||||||
|
assertEquals(3, record.getInt("id"));
|
||||||
|
});
|
||||||
|
|
||||||
|
UpdateInput updateInput = new UpdateInput();
|
||||||
|
updateInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
updateInput.setRecords(List.of(new QRecord().withValue("id", "3").withValue("name", "Bartholomew")));
|
||||||
|
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||||
|
|
||||||
|
// not sure what to assert in here...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testUpdateEmptyInputList() throws QException
|
||||||
|
{
|
||||||
|
UpdateInput updateInput = new UpdateInput();
|
||||||
|
updateInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
updateInput.setRecords(List.of());
|
||||||
|
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testUpdateError() throws QException
|
||||||
|
{
|
||||||
|
////////////////////////////////////////
|
||||||
|
// avoid the fully mocked makeRequest //
|
||||||
|
////////////////////////////////////////
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(500).withContent("""
|
||||||
|
{"error": "Server error"}
|
||||||
|
"""));
|
||||||
|
|
||||||
|
UpdateInput updateInput = new UpdateInput();
|
||||||
|
updateInput.setRecords(List.of(new QRecord().withValue("name", "Milhouse")));
|
||||||
|
updateInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// note - right now this is inconsistent with insertAction (and rdbms update), //
|
||||||
|
// where errors are placed in the records, rather than thrown... //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
assertThatThrownBy(() -> new UpdateAction().execute(updateInput)).hasRootCauseInstanceOf(Exception.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testMakeRequest() throws QException
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this will make it not use the mock makeRequest method, //
|
||||||
|
// but instead the mock executeHttpRequest, so we can test code from the base makeRequest //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"id": 3, "name": "Bart"},
|
||||||
|
""");
|
||||||
|
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
getInput.setPrimaryKey(3);
|
||||||
|
GetOutput getOutput = new GetAction().execute(getInput);
|
||||||
|
assertEquals(3, getOutput.getRecord().getValueInteger("id"));
|
||||||
|
assertEquals("Bart", getOutput.getRecord().getValueString("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void test429Then200() throws QException
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this will make it not use the mock makeRequest method, //
|
||||||
|
// but instead the mock executeHttpRequest, so we can test code from the base makeRequest //
|
||||||
|
// specifically, that we can get one 429, and then eventually a 200 //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(429).withContent("Try again"));
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"id": 3, "name": "Bart"},
|
||||||
|
""");
|
||||||
|
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
getInput.setPrimaryKey(3);
|
||||||
|
GetOutput getOutput = new GetAction().execute(getInput);
|
||||||
|
assertEquals(3, getOutput.getRecord().getValueInteger("id"));
|
||||||
|
assertEquals("Bart", getOutput.getRecord().getValueString("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testTooMany429() throws QException
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this will make it not use the mock makeRequest method, //
|
||||||
|
// but instead the mock executeHttpRequest, so we can test code from the base makeRequest //
|
||||||
|
// specifically, that after too many 429's we get an error //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(429).withContent("Try again"));
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(429).withContent("Try again"));
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(429).withContent("Try again"));
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse(new QHttpResponse().withStatusCode(429).withContent("Try again"));
|
||||||
|
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
getInput.setPrimaryKey(3);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> new GetAction().execute(getInput)).hasRootCauseInstanceOf(RateLimitException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testApiLogs() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = QContext.getQInstance();
|
||||||
|
OutboundAPILogMetaDataProvider.defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
|
||||||
|
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"id": 6}
|
||||||
|
""");
|
||||||
|
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withValue("name", "Milhouse")));
|
||||||
|
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||||
|
assertEquals(6, insertOutput.getRecords().get(0).getValueInteger("id"));
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// the outbound api log is inserted async, so... do or do not, and sleep some if needed //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
QueryOutput apiLogRecords = null;
|
||||||
|
int tries = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
SleepUtils.sleep(10, TimeUnit.MILLISECONDS);
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(OutboundAPILog.TABLE_NAME);
|
||||||
|
apiLogRecords = new QueryAction().execute(queryInput);
|
||||||
|
}
|
||||||
|
while(apiLogRecords.getRecords().isEmpty() && tries++ < 10);
|
||||||
|
|
||||||
|
assertEquals(1, apiLogRecords.getRecords().size());
|
||||||
|
assertEquals("POST", apiLogRecords.getRecords().get(0).getValueString("method"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testBasicAuthApiKey() throws QException
|
||||||
|
{
|
||||||
|
APIBackendMetaData backend = (APIBackendMetaData) QContext.getQInstance().getBackend(TestUtils.MOCK_BACKEND_NAME);
|
||||||
|
backend.setAuthorizationType(AuthorizationType.BASIC_AUTH_API_KEY);
|
||||||
|
backend.setApiKey("9876-WXYZ");
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this will make it not use the mock makeRequest method, //
|
||||||
|
// but instead the mock executeHttpRequest, so we can test code from the base makeRequest //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"id": 3, "name": "Bart"},
|
||||||
|
""");
|
||||||
|
|
||||||
|
mockApiUtilsHelper.setMockRequestAsserter(request ->
|
||||||
|
{
|
||||||
|
Header authHeader = request.getFirstHeader("Authorization");
|
||||||
|
assertTrue(authHeader.getValue().startsWith("Basic "));
|
||||||
|
String apiKey = new String(Base64.getDecoder().decode(authHeader.getValue().replace("Basic ", "")), StandardCharsets.UTF_8);
|
||||||
|
assertEquals("9876-WXYZ", apiKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
runSimpleGetAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testBasicAuthUsernamePassword() throws QException
|
||||||
|
{
|
||||||
|
APIBackendMetaData backend = (APIBackendMetaData) QContext.getQInstance().getBackend(TestUtils.MOCK_BACKEND_NAME);
|
||||||
|
backend.setAuthorizationType(AuthorizationType.BASIC_AUTH_USERNAME_PASSWORD);
|
||||||
|
backend.setUsername("god");
|
||||||
|
backend.setPassword("5fingers");
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this will make it not use the mock makeRequest method, //
|
||||||
|
// but instead the mock executeHttpRequest, so we can test code from the base makeRequest //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"id": 3, "name": "Bart"},
|
||||||
|
""");
|
||||||
|
|
||||||
|
mockApiUtilsHelper.setMockRequestAsserter(request ->
|
||||||
|
{
|
||||||
|
Header authHeader = request.getFirstHeader("Authorization");
|
||||||
|
assertTrue(authHeader.getValue().startsWith("Basic "));
|
||||||
|
String usernamePassword = new String(Base64.getDecoder().decode(authHeader.getValue().replace("Basic ", "")), StandardCharsets.UTF_8);
|
||||||
|
assertEquals("god:5fingers", usernamePassword);
|
||||||
|
});
|
||||||
|
|
||||||
|
runSimpleGetAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOAuth2ValidToken() throws QException
|
||||||
|
{
|
||||||
|
APIBackendMetaData backend = (APIBackendMetaData) QContext.getQInstance().getBackend(TestUtils.MOCK_BACKEND_NAME);
|
||||||
|
backend.setAuthorizationType(AuthorizationType.OAUTH2);
|
||||||
|
backend.withCustomValue("accessToken", "validToken");
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this will make it not use the mock makeRequest method, //
|
||||||
|
// but instead the mock executeHttpRequest, so we can test code from the base makeRequest //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"id": 3, "name": "Bart"},
|
||||||
|
""");
|
||||||
|
|
||||||
|
mockApiUtilsHelper.setMockRequestAsserter(request ->
|
||||||
|
{
|
||||||
|
Header authHeader = request.getFirstHeader("Authorization");
|
||||||
|
assertTrue(authHeader.getValue().startsWith("Bearer "));
|
||||||
|
String token = authHeader.getValue().replace("Bearer ", "");
|
||||||
|
assertEquals("validToken", token);
|
||||||
|
});
|
||||||
|
|
||||||
|
runSimpleGetAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOAuth2NullToken() throws QException
|
||||||
|
{
|
||||||
|
APIBackendMetaData backend = (APIBackendMetaData) QContext.getQInstance().getBackend(TestUtils.MOCK_BACKEND_NAME);
|
||||||
|
backend.setAuthorizationType(AuthorizationType.OAUTH2);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this will make it not use the mock makeRequest method, //
|
||||||
|
// but instead the mock executeHttpRequest, so we can test code from the base makeRequest //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
mockApiUtilsHelper.setUseMock(false);
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"access_token": "myNewToken"}
|
||||||
|
""");
|
||||||
|
mockApiUtilsHelper.enqueueMockResponse("""
|
||||||
|
{"id": 3, "name": "Bart"},
|
||||||
|
""");
|
||||||
|
|
||||||
|
GetOutput getOutput = runSimpleGetAction();
|
||||||
|
assertEquals(3, getOutput.getRecord().getValueInteger("id"));
|
||||||
|
assertEquals("Bart", getOutput.getRecord().getValueString("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static GetOutput runSimpleGetAction() throws QException
|
||||||
|
{
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setTableName(TestUtils.MOCK_TABLE_NAME);
|
||||||
|
getInput.setPrimaryKey(3);
|
||||||
|
return (new GetAction().execute(getInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. 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.module.api.mocks;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.actions.BaseAPIActionUtil;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.actions.QHttpResponse;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.methods.HttpRequestBase;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class MockApiActionUtils extends BaseAPIActionUtil
|
||||||
|
{
|
||||||
|
public static MockApiUtilsHelper mockApiUtilsHelper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public QHttpResponse makeRequest(QTableMetaData table, HttpRequestBase request) throws QException
|
||||||
|
{
|
||||||
|
return (mockApiUtilsHelper.defaultMockMakeRequest(mockApiUtilsHelper, table, request, () -> super.makeRequest(table, request)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
protected CloseableHttpResponse executeHttpRequest(HttpRequestBase request, CloseableHttpClient httpClient) throws IOException
|
||||||
|
{
|
||||||
|
runMockAsserter(request);
|
||||||
|
return new MockHttpResponse(mockApiUtilsHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static void runMockAsserter(HttpRequestBase request)
|
||||||
|
{
|
||||||
|
if(mockApiUtilsHelper.getMockRequestAsserter() != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mockApiUtilsHelper.getMockRequestAsserter().run(request);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new RuntimeException("Error running mock request asserter", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
protected CloseableHttpResponse executeOAuthTokenRequest(CloseableHttpClient client, HttpPost request) throws IOException
|
||||||
|
{
|
||||||
|
runMockAsserter(request);
|
||||||
|
return new MockHttpResponse(mockApiUtilsHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
protected int getInitialRateLimitBackoffMillis()
|
||||||
|
{
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. 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.module.api.mocks;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeConsumer;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeSupplier;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.actions.QHttpResponse;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.methods.HttpRequestBase;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class MockApiUtilsHelper
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(MockApiUtilsHelper.class);
|
||||||
|
|
||||||
|
private boolean useMock = true;
|
||||||
|
private Deque<QHttpResponse> mockResponseQueue = new ArrayDeque<>();
|
||||||
|
private UnsafeConsumer<HttpRequestBase, ? extends Throwable> mockRequestAsserter = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void enqueueMockResponse(String json)
|
||||||
|
{
|
||||||
|
mockResponseQueue.addLast(new QHttpResponse()
|
||||||
|
.withStatusCode(200)
|
||||||
|
.withContent(json)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void enqueueMockResponse(QHttpResponse qHttpResponse)
|
||||||
|
{
|
||||||
|
mockResponseQueue.addLast(qHttpResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QHttpResponse returnMockResponseFromQueue(HttpRequestBase request) throws QException
|
||||||
|
{
|
||||||
|
if(getMockRequestAsserter() != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
getMockRequestAsserter().run(request);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QException("Error running mock request asserter", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mockResponseQueue.isEmpty())
|
||||||
|
{
|
||||||
|
fail("No mock response is in the queue for " + request.getMethod() + " " + request.getURI());
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Returning mock http response for " + request.getMethod() + " " + request.getURI());
|
||||||
|
return (mockResponseQueue.removeFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for useMock
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean getUseMock()
|
||||||
|
{
|
||||||
|
return (this.useMock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for useMock
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setUseMock(boolean useMock)
|
||||||
|
{
|
||||||
|
this.useMock = useMock;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for useMock
|
||||||
|
*******************************************************************************/
|
||||||
|
public MockApiUtilsHelper withUseMock(boolean useMock)
|
||||||
|
{
|
||||||
|
this.useMock = useMock;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for mockResponseQueue
|
||||||
|
*******************************************************************************/
|
||||||
|
public Deque<QHttpResponse> getMockResponseQueue()
|
||||||
|
{
|
||||||
|
return (this.mockResponseQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for mockResponseQueue
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setMockResponseQueue(Deque<QHttpResponse> mockResponseQueue)
|
||||||
|
{
|
||||||
|
this.mockResponseQueue = mockResponseQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for mockResponseQueue
|
||||||
|
*******************************************************************************/
|
||||||
|
public MockApiUtilsHelper withMockResponseQueue(Deque<QHttpResponse> mockResponseQueue)
|
||||||
|
{
|
||||||
|
this.mockResponseQueue = mockResponseQueue;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for mockRequestAsserter
|
||||||
|
*******************************************************************************/
|
||||||
|
public UnsafeConsumer<HttpRequestBase, ? extends Throwable> getMockRequestAsserter()
|
||||||
|
{
|
||||||
|
return (this.mockRequestAsserter);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for mockRequestAsserter
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setMockRequestAsserter(UnsafeConsumer<HttpRequestBase, ? extends Throwable> mockRequestAsserter)
|
||||||
|
{
|
||||||
|
this.mockRequestAsserter = mockRequestAsserter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for mockRequestAsserter
|
||||||
|
*******************************************************************************/
|
||||||
|
public MockApiUtilsHelper withMockRequestAsserter(UnsafeConsumer<HttpRequestBase, ? extends Throwable> mockRequestAsserter)
|
||||||
|
{
|
||||||
|
this.mockRequestAsserter = mockRequestAsserter;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QHttpResponse defaultMockMakeRequest(MockApiUtilsHelper mockApiUtilsHelper, QTableMetaData table, HttpRequestBase request, UnsafeSupplier<QHttpResponse, QException> superMethod) throws QException
|
||||||
|
{
|
||||||
|
if(!mockApiUtilsHelper.getUseMock())
|
||||||
|
{
|
||||||
|
QHttpResponse superResponse = superMethod.get();
|
||||||
|
System.out.println("== non-mock response content: ==");
|
||||||
|
System.out.println("Code: " + superResponse.getStatusCode());
|
||||||
|
System.out.println(superResponse.getContent());
|
||||||
|
System.out.println("== ==");
|
||||||
|
return (superResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mockApiUtilsHelper.returnMockResponseFromQueue(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static String readRequestBody(HttpRequestBase request) throws IOException
|
||||||
|
{
|
||||||
|
return (StringUtils.join("\n", IOUtils.readLines(((HttpPost) request).getEntity().getContent())));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,302 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. 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.module.api.mocks;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Locale;
|
||||||
|
import com.kingsrook.qqq.backend.module.api.actions.QHttpResponse;
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.HeaderIterator;
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.ProtocolVersion;
|
||||||
|
import org.apache.http.StatusLine;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.entity.BasicHttpEntity;
|
||||||
|
import org.apache.http.message.BasicStatusLine;
|
||||||
|
import org.apache.http.params.HttpParams;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class MockHttpResponse implements CloseableHttpResponse
|
||||||
|
{
|
||||||
|
private final MockApiUtilsHelper mockApiUtilsHelper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public MockHttpResponse(MockApiUtilsHelper mockApiUtilsHelper)
|
||||||
|
{
|
||||||
|
this.mockApiUtilsHelper = mockApiUtilsHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusLine getStatusLine()
|
||||||
|
{
|
||||||
|
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
|
||||||
|
|
||||||
|
if(!mockApiUtilsHelper.getMockResponseQueue().isEmpty())
|
||||||
|
{
|
||||||
|
QHttpResponse qHttpResponse = mockApiUtilsHelper.getMockResponseQueue().peekFirst();
|
||||||
|
return (new BasicStatusLine(protocolVersion, qHttpResponse.getStatusCode(), qHttpResponse.getStatusReasonPhrase()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (new BasicStatusLine(protocolVersion, 200, "OK"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatusLine(StatusLine statusLine)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatusLine(ProtocolVersion protocolVersion, int i)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatusLine(ProtocolVersion protocolVersion, int i, String s)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatusCode(int i) throws IllegalStateException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReasonPhrase(String s) throws IllegalStateException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpEntity getEntity()
|
||||||
|
{
|
||||||
|
BasicHttpEntity basicHttpEntity = new BasicHttpEntity();
|
||||||
|
|
||||||
|
if(!mockApiUtilsHelper.getMockResponseQueue().isEmpty())
|
||||||
|
{
|
||||||
|
QHttpResponse qHttpResponse = mockApiUtilsHelper.getMockResponseQueue().removeFirst();
|
||||||
|
basicHttpEntity.setContent(new ByteArrayInputStream(qHttpResponse.getContent().getBytes()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
basicHttpEntity.setContent(new ByteArrayInputStream("".getBytes()));
|
||||||
|
}
|
||||||
|
return (basicHttpEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEntity(HttpEntity httpEntity)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Locale getLocale()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLocale(Locale locale)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolVersion getProtocolVersion()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsHeader(String s)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Header[] getHeaders(String s)
|
||||||
|
{
|
||||||
|
return new Header[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Header getFirstHeader(String s)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Header getLastHeader(String s)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Header[] getAllHeaders()
|
||||||
|
{
|
||||||
|
return new Header[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHeader(Header header)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHeader(String s, String s1)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(Header header)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(String s, String s1)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeaders(Header[] headers)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeHeader(Header header)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeHeaders(String s)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HeaderIterator headerIterator()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HeaderIterator headerIterator(String s)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpParams getParams()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setParams(HttpParams httpParams)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -50,6 +50,7 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
@ -87,7 +88,7 @@ public class QueryManager
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
void processResultSet(ResultSet rs) throws SQLException;
|
void processResultSet(ResultSet rs) throws SQLException, QException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -95,7 +96,7 @@ public class QueryManager
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static void executeStatement(Connection connection, String sql, ResultSetProcessor processor, Object... params) throws SQLException
|
public static void executeStatement(Connection connection, String sql, ResultSetProcessor processor, Object... params) throws SQLException, QException
|
||||||
{
|
{
|
||||||
PreparedStatement statement = null;
|
PreparedStatement statement = null;
|
||||||
try
|
try
|
||||||
@ -118,7 +119,7 @@ public class QueryManager
|
|||||||
** Let the caller provide their own prepared statement (e.g., possibly with some
|
** Let the caller provide their own prepared statement (e.g., possibly with some
|
||||||
** customized settings/optimizations).
|
** customized settings/optimizations).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static void executeStatement(PreparedStatement statement, ResultSetProcessor processor, Object... params) throws SQLException
|
public static void executeStatement(PreparedStatement statement, ResultSetProcessor processor, Object... params) throws SQLException, QException
|
||||||
{
|
{
|
||||||
ResultSet resultSet = null;
|
ResultSet resultSet = null;
|
||||||
|
|
||||||
|
@ -33,10 +33,8 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<!-- props specifically to this module -->
|
<!-- props specifically to this module -->
|
||||||
|
<!-- none at this time -->
|
||||||
|
|
||||||
<!-- todo - remove these!! -->
|
|
||||||
<coverage.instructionCoveredRatioMinimum>0.10</coverage.instructionCoveredRatioMinimum>
|
|
||||||
<coverage.classCoveredRatioMinimum>0.10</coverage.classCoveredRatioMinimum>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -64,6 +64,11 @@ public class QRecordApiAdapter
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static Map<String, Serializable> qRecordToApiMap(QRecord record, String tableName, String apiName, String apiVersion) throws QException
|
public static Map<String, Serializable> qRecordToApiMap(QRecord record, String tableName, String apiName, String apiVersion) throws QException
|
||||||
{
|
{
|
||||||
|
if(record == null)
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
List<QFieldMetaData> tableApiFields = getTableApiFieldList(new ApiNameVersionAndTableName(apiName, apiVersion, tableName));
|
List<QFieldMetaData> tableApiFields = getTableApiFieldList(new ApiNameVersionAndTableName(apiName, apiVersion, tableName));
|
||||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,7 @@ import java.io.Serializable;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import com.kingsrook.qqq.api.model.metadata.APILogMetaDataProvider;
|
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataProvider;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||||
@ -49,7 +49,7 @@ public class APILog extends QRecordEntity
|
|||||||
@QField(isEditable = false)
|
@QField(isEditable = false)
|
||||||
private Instant timestamp;
|
private Instant timestamp;
|
||||||
|
|
||||||
@QField(possibleValueSourceName = APILogMetaDataProvider.TABLE_NAME_API_LOG_USER, label = "User")
|
@QField(possibleValueSourceName = ApiInstanceMetaDataProvider.TABLE_NAME_API_LOG_USER, label = "User")
|
||||||
private Integer apiLogUserId;
|
private Integer apiLogUserId;
|
||||||
|
|
||||||
@QField(possibleValueSourceName = "apiMethod")
|
@QField(possibleValueSourceName = "apiMethod")
|
||||||
|
@ -50,7 +50,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class APILogMetaDataProvider
|
public class ApiInstanceMetaDataProvider
|
||||||
{
|
{
|
||||||
public static final String TABLE_NAME_API_LOG = "apiLog";
|
public static final String TABLE_NAME_API_LOG = "apiLog";
|
||||||
public static final String TABLE_NAME_API_LOG_USER = "apiLogUser";
|
public static final String TABLE_NAME_API_LOG_USER = "apiLogUser";
|
||||||
@ -105,21 +105,32 @@ public class APILogMetaDataProvider
|
|||||||
new QPossibleValue<>(500, "500 (Internal Server Error)")
|
new QPossibleValue<>(500, "500 (Internal Server Error)")
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// loop over api names and versions, building out possible values sources //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
List<QPossibleValue<?>> apiNamePossibleValues = new ArrayList<>();
|
||||||
List<QPossibleValue<?>> apiVersionPossibleValues = new ArrayList<>();
|
List<QPossibleValue<?>> apiVersionPossibleValues = new ArrayList<>();
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
// todo... this, this whole thing, should probably have "which api" as another field too... ugh. //
|
// todo... apiName should maybe be a field on apiLog table, eh? //
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
TreeSet<APIVersion> allVersions = new TreeSet<>();
|
TreeSet<APIVersion> allVersions = new TreeSet<>();
|
||||||
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(instance);
|
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(instance);
|
||||||
for(Map.Entry<String, ApiInstanceMetaData> entry : apiInstanceMetaDataContainer.getApis().entrySet())
|
for(Map.Entry<String, ApiInstanceMetaData> entry : apiInstanceMetaDataContainer.getApis().entrySet())
|
||||||
{
|
{
|
||||||
|
apiNamePossibleValues.add(new QPossibleValue<>(entry.getKey(), entry.getValue().getLabel()));
|
||||||
|
|
||||||
ApiInstanceMetaData apiInstanceMetaData = entry.getValue();
|
ApiInstanceMetaData apiInstanceMetaData = entry.getValue();
|
||||||
allVersions.addAll(apiInstanceMetaData.getPastVersions());
|
allVersions.addAll(apiInstanceMetaData.getPastVersions());
|
||||||
allVersions.addAll(apiInstanceMetaData.getSupportedVersions());
|
allVersions.addAll(apiInstanceMetaData.getSupportedVersions());
|
||||||
allVersions.addAll(apiInstanceMetaData.getFutureVersions());
|
allVersions.addAll(apiInstanceMetaData.getFutureVersions());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instance.addPossibleValueSource(new QPossibleValueSource()
|
||||||
|
.withName("apiName")
|
||||||
|
.withType(QPossibleValueSourceType.ENUM)
|
||||||
|
.withEnumValues(apiNamePossibleValues));
|
||||||
|
|
||||||
for(APIVersion version : allVersions)
|
for(APIVersion version : allVersions)
|
||||||
{
|
{
|
||||||
apiVersionPossibleValues.add(new QPossibleValue<>(version.toString()));
|
apiVersionPossibleValues.add(new QPossibleValue<>(version.toString()));
|
@ -0,0 +1,260 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. 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.api.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.api.actions.ApiImplementation;
|
||||||
|
import com.kingsrook.qqq.api.actions.QRecordApiAdapter;
|
||||||
|
import com.kingsrook.qqq.api.model.APIVersion;
|
||||||
|
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
|
||||||
|
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataContainer;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Object injected into script context, for interfacing with a QQQ API.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class ApiScriptUtils implements Serializable
|
||||||
|
{
|
||||||
|
private String apiName;
|
||||||
|
private String apiVersion;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public ApiScriptUtils(String apiName, String apiVersion)
|
||||||
|
{
|
||||||
|
setApiName(apiName);
|
||||||
|
setApiVersion(apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static ArrayList<Map<String, Serializable>> qRecordListToApiRecordList(List<QRecord> qRecordList, String tableName, String apiName, String apiVersion) throws QException
|
||||||
|
{
|
||||||
|
if(qRecordList == null)
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<Map<String, Serializable>> rs = new ArrayList<>();
|
||||||
|
for(QRecord qRecord : qRecordList)
|
||||||
|
{
|
||||||
|
rs.add(QRecordApiAdapter.qRecordToApiMap(qRecord, tableName, apiName, apiVersion));
|
||||||
|
}
|
||||||
|
return (rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for apiName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setApiName(String apiName)
|
||||||
|
{
|
||||||
|
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(QContext.getQInstance());
|
||||||
|
if(apiInstanceMetaDataContainer.getApis().containsKey(apiName))
|
||||||
|
{
|
||||||
|
this.apiName = apiName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new IllegalArgumentException("[" + apiName + "] is not a valid API name. Valid values are: " + apiInstanceMetaDataContainer.getApis().keySet()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for apiVersion
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setApiVersion(String apiVersion)
|
||||||
|
{
|
||||||
|
if(apiName == null)
|
||||||
|
{
|
||||||
|
throw (new IllegalArgumentException("You must set apiName before setting apiVersion."));
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(QContext.getQInstance());
|
||||||
|
ApiInstanceMetaData apiInstanceMetaData = apiInstanceMetaDataContainer.getApis().get(apiName);
|
||||||
|
if(apiInstanceMetaData.getSupportedVersions().contains(new APIVersion(apiVersion)))
|
||||||
|
{
|
||||||
|
this.apiVersion = apiVersion;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new IllegalArgumentException("[" + apiVersion + "] is not a supported version for this API. Supported versions are: " + apiInstanceMetaData.getSupportedVersions()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void validateApiNameAndVersion(String description)
|
||||||
|
{
|
||||||
|
if(apiName == null || apiVersion == null)
|
||||||
|
{
|
||||||
|
throw (new IllegalStateException("Both apiName and apiVersion must be set before calling this method (" + description + ")."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, Serializable> get(String tableApiName, Object primaryKey) throws QException
|
||||||
|
{
|
||||||
|
validateApiNameAndVersion("get(" + tableApiName + "," + primaryKey + ")");
|
||||||
|
return (ApiImplementation.get(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(primaryKey)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, Serializable> query(String tableName, String queryString) throws QException
|
||||||
|
{
|
||||||
|
validateApiNameAndVersion("query(" + tableName + ")");
|
||||||
|
Map<String, List<String>> paramMap = parseQueryString(queryString);
|
||||||
|
return (ApiImplementation.query(getApiInstanceMetaData(), apiVersion, tableName, paramMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, Serializable> insert(String tableApiName, Object body) throws QException
|
||||||
|
{
|
||||||
|
validateApiNameAndVersion("insert(" + tableApiName + ")");
|
||||||
|
return (ApiImplementation.insert(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(body)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<Map<String, Serializable>> bulkInsert(String tableApiName, Object body) throws QException
|
||||||
|
{
|
||||||
|
validateApiNameAndVersion("bulkInsert(" + tableApiName + ")");
|
||||||
|
return (ApiImplementation.bulkInsert(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(body)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void update(String tableApiName, Object primaryKey, Object body) throws QException
|
||||||
|
{
|
||||||
|
validateApiNameAndVersion("update(" + tableApiName + "," + primaryKey + ")");
|
||||||
|
ApiImplementation.update(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(primaryKey), String.valueOf(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<Map<String, Serializable>> bulkUpdate(String tableApiName, Object body) throws QException
|
||||||
|
{
|
||||||
|
validateApiNameAndVersion("bulkUpdate(" + tableApiName + ")");
|
||||||
|
return (ApiImplementation.bulkUpdate(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(body)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void delete(String tableApiName, Object primaryKey) throws QException
|
||||||
|
{
|
||||||
|
validateApiNameAndVersion("delete(" + tableApiName + "," + primaryKey + ")");
|
||||||
|
ApiImplementation.delete(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(primaryKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<Map<String, Serializable>> bulkDelete(String tableApiName, Object body) throws QException
|
||||||
|
{
|
||||||
|
validateApiNameAndVersion("bulkDelete(" + tableApiName + ")");
|
||||||
|
return (ApiImplementation.bulkDelete(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(body)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private ApiInstanceMetaData getApiInstanceMetaData()
|
||||||
|
{
|
||||||
|
ApiInstanceMetaDataContainer apiInstanceMetaDataContainer = ApiInstanceMetaDataContainer.of(QContext.getQInstance());
|
||||||
|
ApiInstanceMetaData apiInstanceMetaData = apiInstanceMetaDataContainer.getApiInstanceMetaData(apiName);
|
||||||
|
return apiInstanceMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static Map<String, List<String>> parseQueryString(String queryString)
|
||||||
|
{
|
||||||
|
Map<String, List<String>> paramMap = new LinkedHashMap<>();
|
||||||
|
if(queryString != null)
|
||||||
|
{
|
||||||
|
for(String nameValuePair : queryString.split("&"))
|
||||||
|
{
|
||||||
|
String[] nameValue = nameValuePair.split("=", 2);
|
||||||
|
if(nameValue.length == 2)
|
||||||
|
{
|
||||||
|
paramMap.computeIfAbsent(nameValue[0], (k) -> new ArrayList<>());
|
||||||
|
paramMap.get(nameValue[0]).add(nameValue[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paramMap;
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@
|
|||||||
package com.kingsrook.qqq.api;
|
package com.kingsrook.qqq.api;
|
||||||
|
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.api.model.APIVersion;
|
import com.kingsrook.qqq.api.model.APIVersion;
|
||||||
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
|
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
|
||||||
@ -30,7 +31,11 @@ import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
|
|||||||
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
|
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
|
||||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
||||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
|
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaDataContainer;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
|
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.QAuthenticationType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
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.QInstance;
|
||||||
@ -324,4 +329,40 @@ public class TestUtils
|
|||||||
.withOrderBy(new QFilterOrderBy("key"));
|
.withOrderBy(new QFilterOrderBy("key"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static void insertPersonRecord(Integer id, String firstName, String lastName) throws QException
|
||||||
|
{
|
||||||
|
insertPersonRecord(id, firstName, lastName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static void insertPersonRecord(Integer id, String firstName, String lastName, LocalDate birthDate) throws QException
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withValue("id", id).withValue("firstName", firstName).withValue("lastName", lastName).withValue("birthDate", birthDate)));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static void insertSimpsons() throws QException
|
||||||
|
{
|
||||||
|
insertPersonRecord(1, "Homer", "Simpson");
|
||||||
|
insertPersonRecord(2, "Marge", "Simpson");
|
||||||
|
insertPersonRecord(3, "Bart", "Simpson");
|
||||||
|
insertPersonRecord(4, "Lisa", "Simpson");
|
||||||
|
insertPersonRecord(5, "Maggie", "Simpson");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,8 @@ import org.json.JSONObject;
|
|||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static com.kingsrook.qqq.api.TestUtils.insertPersonRecord;
|
||||||
|
import static com.kingsrook.qqq.api.TestUtils.insertSimpsons;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
@ -145,25 +147,29 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
@Test
|
@Test
|
||||||
void testRandom404s()
|
void testRandom404s()
|
||||||
{
|
{
|
||||||
for(String method : new String[] { "get", "post", "patch", "delete" })
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("get", BASE_URL + "/api/" + VERSION + "/notATable/").asString());
|
||||||
{
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find a table named notATable in this api", Unirest.request("get", BASE_URL + "/api/" + VERSION + "/notATable/notAnId").asString());
|
||||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request(method, BASE_URL + "/api/" + VERSION + "/notATable/").asString());
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("get", BASE_URL + "/api/" + VERSION + "/person/1/2").asString());
|
||||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request(method, BASE_URL + "/api/" + VERSION + "/notATable/notAnId").asString());
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("get", BASE_URL + "/api/foo").asString());
|
||||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request(method, BASE_URL + "/api/" + VERSION + "/person/1/2").asString());
|
assertErrorResponse(HttpStatus.OK_200, null, Unirest.request("get", BASE_URL + "/api/").asString()); // this path returns the doc site for a GET
|
||||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request(method, BASE_URL + "/api/foo").asString());
|
|
||||||
|
|
||||||
if(method.equals("get"))
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find a table named notATable in this api", Unirest.request("post", BASE_URL + "/api/" + VERSION + "/notATable/").asString());
|
||||||
{
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("post", BASE_URL + "/api/" + VERSION + "/notATable/notAnId").asString());
|
||||||
//////////////////////////////////////////////
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("post", BASE_URL + "/api/" + VERSION + "/person/1/2").asString());
|
||||||
// this path returns the doc site for a GET //
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("post", BASE_URL + "/api/foo").asString());
|
||||||
//////////////////////////////////////////////
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("post", BASE_URL + "/api/").asString());
|
||||||
assertErrorResponse(HttpStatus.OK_200, null, Unirest.request(method, BASE_URL + "/api/").asString());
|
|
||||||
}
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("patch", BASE_URL + "/api/" + VERSION + "/notATable/").asString());
|
||||||
else
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find a table named notATable in this api", Unirest.request("patch", BASE_URL + "/api/" + VERSION + "/notATable/notAnId").asString());
|
||||||
{
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("patch", BASE_URL + "/api/" + VERSION + "/person/1/2").asString());
|
||||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request(method, BASE_URL + "/api/").asString());
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("patch", BASE_URL + "/api/foo").asString());
|
||||||
}
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("patch", BASE_URL + "/api/").asString());
|
||||||
}
|
|
||||||
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("delete", BASE_URL + "/api/" + VERSION + "/notATable/").asString());
|
||||||
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find a table named notATable in this api", Unirest.request("delete", BASE_URL + "/api/" + VERSION + "/notATable/notAnId").asString());
|
||||||
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("delete", BASE_URL + "/api/" + VERSION + "/person/1/2").asString());
|
||||||
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("delete", BASE_URL + "/api/foo").asString());
|
||||||
|
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request("delete", BASE_URL + "/api/").asString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1240,43 +1246,6 @@ class QJavalinApiHandlerTest extends BaseTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private static void insertPersonRecord(Integer id, String firstName, String lastName) throws QException
|
|
||||||
{
|
|
||||||
insertPersonRecord(id, firstName, lastName, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private static void insertPersonRecord(Integer id, String firstName, String lastName, LocalDate birthDate) throws QException
|
|
||||||
{
|
|
||||||
InsertInput insertInput = new InsertInput();
|
|
||||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
|
||||||
insertInput.setRecords(List.of(new QRecord().withValue("id", id).withValue("firstName", firstName).withValue("lastName", lastName).withValue("birthDate", birthDate)));
|
|
||||||
new InsertAction().execute(insertInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private static void insertSimpsons() throws QException
|
|
||||||
{
|
|
||||||
insertPersonRecord(1, "Homer", "Simpson");
|
|
||||||
insertPersonRecord(2, "Marge", "Simpson");
|
|
||||||
insertPersonRecord(3, "Bart", "Simpson");
|
|
||||||
insertPersonRecord(4, "Lisa", "Simpson");
|
|
||||||
insertPersonRecord(5, "Maggie", "Simpson");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -0,0 +1,273 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. 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.api.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.api.BaseTest;
|
||||||
|
import com.kingsrook.qqq.api.TestUtils;
|
||||||
|
import com.kingsrook.qqq.api.javalin.QBadRequestException;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static com.kingsrook.qqq.api.TestUtils.insertSimpsons;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for com.kingsrook.qqq.api.utils.ApiScriptUtils
|
||||||
|
*******************************************************************************/
|
||||||
|
class ApiScriptUtilsTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testSetApiNameAndApiVersion()
|
||||||
|
{
|
||||||
|
ApiScriptUtils apiScriptUtils = newDefaultApiScriptUtils();
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> apiScriptUtils.setApiName("not an api"))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessageContaining("not a valid API name");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> apiScriptUtils.setApiVersion("not a version"))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessageContaining("not a supported version");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> new ApiScriptUtils("not an api", TestUtils.CURRENT_API_VERSION))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessageContaining("not a valid API name");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> new ApiScriptUtils(TestUtils.ALTERNATIVE_API_NAME, "not a version"))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessageContaining("not a supported version");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testGet() throws QException
|
||||||
|
{
|
||||||
|
ApiScriptUtils apiScriptUtils = newDefaultApiScriptUtils();
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> apiScriptUtils.get(TestUtils.TABLE_NAME_PERSON, 1))
|
||||||
|
.isInstanceOf(QNotFoundException.class);
|
||||||
|
|
||||||
|
insertSimpsons();
|
||||||
|
|
||||||
|
Map<String, Serializable> result = apiScriptUtils.get(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
|
assertEquals("Homer", result.get("firstName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testQuery() throws QException
|
||||||
|
{
|
||||||
|
ApiScriptUtils apiScriptUtils = newDefaultApiScriptUtils();
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> apiScriptUtils.query(TestUtils.TABLE_NAME_PERSON, "foo=bar"))
|
||||||
|
.isInstanceOf(QBadRequestException.class)
|
||||||
|
.hasMessageContaining("Unrecognized filter criteria field: foo");
|
||||||
|
|
||||||
|
insertSimpsons();
|
||||||
|
|
||||||
|
Map<String, Serializable> result = apiScriptUtils.query(TestUtils.TABLE_NAME_PERSON, "id=2");
|
||||||
|
assertEquals(1, result.get("count"));
|
||||||
|
assertEquals(1, ((List<?>) result.get("records")).size());
|
||||||
|
assertEquals("Marge", ((Map<?, ?>) ((List<?>) result.get("records")).get(0)).get("firstName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testInsert() throws QException
|
||||||
|
{
|
||||||
|
ApiScriptUtils apiScriptUtils = newDefaultApiScriptUtils();
|
||||||
|
Map<String, Serializable> result = apiScriptUtils.insert(TestUtils.TABLE_NAME_PERSON, """
|
||||||
|
{ "firstName": "Mr.", "lastName": "Burns" }
|
||||||
|
""");
|
||||||
|
assertEquals(1, result.get("id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testBulkInsert() throws QException
|
||||||
|
{
|
||||||
|
ApiScriptUtils apiScriptUtils = newDefaultApiScriptUtils();
|
||||||
|
List<Map<String, Serializable>> result = apiScriptUtils.bulkInsert(TestUtils.TABLE_NAME_PERSON, """
|
||||||
|
[
|
||||||
|
{ "firstName": "Mr.", "lastName": "Burns" },
|
||||||
|
{ "firstName": "Waylon", "lastName": "Smithers" }
|
||||||
|
]
|
||||||
|
""");
|
||||||
|
assertEquals(2, result.size());
|
||||||
|
assertEquals(1, result.get(0).get("id"));
|
||||||
|
assertEquals(2, result.get(1).get("id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testUpdate() throws QException
|
||||||
|
{
|
||||||
|
ApiScriptUtils apiScriptUtils = newDefaultApiScriptUtils();
|
||||||
|
String updateJSON = """
|
||||||
|
{ "firstName": "Homer J." }
|
||||||
|
""";
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> apiScriptUtils.update(TestUtils.TABLE_NAME_PERSON, 1, updateJSON))
|
||||||
|
.isInstanceOf(QNotFoundException.class);
|
||||||
|
|
||||||
|
insertSimpsons();
|
||||||
|
|
||||||
|
apiScriptUtils.update(TestUtils.TABLE_NAME_PERSON, 1, updateJSON);
|
||||||
|
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
getInput.setPrimaryKey(1);
|
||||||
|
GetOutput getOutput = new GetAction().execute(getInput);
|
||||||
|
assertEquals("Homer J.", getOutput.getRecord().getValueString("firstName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testBulkUpdate() throws QException
|
||||||
|
{
|
||||||
|
ApiScriptUtils apiScriptUtils = newDefaultApiScriptUtils();
|
||||||
|
|
||||||
|
insertSimpsons();
|
||||||
|
|
||||||
|
String updateJSON = """
|
||||||
|
[
|
||||||
|
{ "id": 1, "firstName": "Homer J." },
|
||||||
|
{ "id": 6, "firstName": "C.M." }
|
||||||
|
]
|
||||||
|
""";
|
||||||
|
|
||||||
|
List<Map<String, Serializable>> result = apiScriptUtils.bulkUpdate(TestUtils.TABLE_NAME_PERSON, updateJSON);
|
||||||
|
|
||||||
|
assertEquals(2, result.size());
|
||||||
|
assertEquals(1, result.get(0).get("id"));
|
||||||
|
assertEquals(6, result.get(1).get("id"));
|
||||||
|
assertEquals(404, result.get(1).get("statusCode"));
|
||||||
|
assertNotNull(result.get(1).get("error"));
|
||||||
|
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
getInput.setPrimaryKey(1);
|
||||||
|
GetOutput getOutput = new GetAction().execute(getInput);
|
||||||
|
assertEquals("Homer J.", getOutput.getRecord().getValueString("firstName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testDelete() throws QException
|
||||||
|
{
|
||||||
|
ApiScriptUtils apiScriptUtils = newDefaultApiScriptUtils();
|
||||||
|
assertThatThrownBy(() -> apiScriptUtils.delete(TestUtils.TABLE_NAME_PERSON, 1))
|
||||||
|
.isInstanceOf(QNotFoundException.class);
|
||||||
|
|
||||||
|
insertSimpsons();
|
||||||
|
|
||||||
|
apiScriptUtils.delete(TestUtils.TABLE_NAME_PERSON, 1);
|
||||||
|
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
getInput.setPrimaryKey(1);
|
||||||
|
GetOutput getOutput = new GetAction().execute(getInput);
|
||||||
|
assertNull(getOutput.getRecord());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testBulkDelete() throws QException
|
||||||
|
{
|
||||||
|
ApiScriptUtils apiScriptUtils = newDefaultApiScriptUtils();
|
||||||
|
|
||||||
|
insertSimpsons();
|
||||||
|
|
||||||
|
List<Map<String, Serializable>> result = apiScriptUtils.bulkDelete(TestUtils.TABLE_NAME_PERSON, "[1,6]");
|
||||||
|
|
||||||
|
assertEquals(2, result.size());
|
||||||
|
assertEquals(1, ValueUtils.getValueAsInteger(result.get(0).get("id")));
|
||||||
|
assertEquals(6, ValueUtils.getValueAsInteger(result.get(1).get("id")));
|
||||||
|
assertEquals(404, result.get(1).get("statusCode"));
|
||||||
|
assertNotNull(result.get(1).get("error"));
|
||||||
|
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
getInput.setPrimaryKey(1);
|
||||||
|
GetOutput getOutput = new GetAction().execute(getInput);
|
||||||
|
assertNull(getOutput.getRecord());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static ApiScriptUtils newDefaultApiScriptUtils()
|
||||||
|
{
|
||||||
|
return (new ApiScriptUtils(TestUtils.API_NAME, TestUtils.CURRENT_API_VERSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -395,18 +395,18 @@ public class QJavalinScriptsHandler
|
|||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
String value = entry.getValue().get(0);
|
String value = entry.getValue().get(0);
|
||||||
|
|
||||||
if(key.equals("code"))
|
switch(key)
|
||||||
{
|
{
|
||||||
input.setCodeReference(new QCodeReference().withInlineCode(value).withCodeType(QCodeType.JAVA_SCRIPT));
|
case "code" -> input.setCodeReference(new QCodeReference().withInlineCode(value).withCodeType(QCodeType.JAVA_SCRIPT));
|
||||||
}
|
case "apiName" -> input.setApiName(value);
|
||||||
else
|
case "apiVersion" -> input.setApiVersion(value);
|
||||||
{
|
default -> inputValues.put(key, value);
|
||||||
inputValues.put(key, value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TestScriptActionInterface scriptTester = QCodeLoader.getAdHoc(TestScriptActionInterface.class, scriptTesterCodeRef);
|
TestScriptActionInterface scriptTester = QCodeLoader.getAdHoc(TestScriptActionInterface.class, scriptTesterCodeRef);
|
||||||
TestScriptOutput output = new TestScriptOutput();
|
TestScriptOutput output = new TestScriptOutput();
|
||||||
|
|
||||||
scriptTester.execute(input, output);
|
scriptTester.execute(input, output);
|
||||||
|
|
||||||
QJavalinAccessLogger.logEndSuccess();
|
QJavalinAccessLogger.logEndSuccess();
|
||||||
|
Reference in New Issue
Block a user