From 86adccddd4e4f0d5b41e63406f0ae1ca5ba2963e Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 12 Jul 2022 07:46:13 -0500 Subject: [PATCH] QQQ-21 feedback from code review; add more types to ValueUtils --- .../core/actions/RunProcessAction.java | 19 ++- .../core/actions/async/AsyncJobCallback.java | 2 +- .../core/actions/async/AsyncJobManager.java | 2 +- .../core/actions/async/AsyncJobStatus.java | 17 +++ .../actions/async/JobGoingAsyncException.java | 3 + .../processes/RunBackendStepRequest.java | 31 ++++ .../qqq/backend/core/model/data/QRecord.java | 29 +++- .../core/model/metadata/QFieldMetaData.java | 2 +- .../processes/QBackendStepMetaData.java | 4 +- .../metadata/processes/QProcessMetaData.java | 2 +- .../metadata/processes/QStepMetaData.java | 4 +- .../QStepMetaDataDeserializer.java | 3 +- .../qqq/backend/core/state/StateType.java | 3 + .../backend/core/utils/ExceptionUtils.java | 2 +- .../qqq/backend/core/utils/ValueUtils.java | 132 +++++++++++++++++- .../actions/ProcessMetaDataActionTest.java | 4 +- .../backend/core/actions/RunProcessTest.java | 82 +++++++++++ .../actions/async/AsyncJobManagerTest.java | 6 +- .../etl/basic/BasicETLProcessTest.java | 2 + .../core/utils/CollectionUtilsTest.java | 4 + .../backend/core/utils/ValueUtilsTest.java | 77 ++++++++++ 21 files changed, 409 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/RunProcessAction.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/RunProcessAction.java index d34326ce..e39c89e8 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/actions/RunProcessAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/RunProcessAction.java @@ -106,7 +106,6 @@ public class RunProcessAction case SKIP -> { LOG.info("Skipping frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)"); - processState.setNextStepName(step.getName()); ////////////////////////////////////////////////////////////////////// // much less error prone in case this code changes in the future... // @@ -166,9 +165,10 @@ public class RunProcessAction /******************************************************************************* - ** + ** When we start running a process (or resuming it), get data in the RunProcessRequest + ** either from the state provider (if they're found, for a resume). *******************************************************************************/ - private ProcessState primeProcessState(RunProcessRequest runProcessRequest, UUIDAndTypeStateKey stateKey) throws QException + ProcessState primeProcessState(RunProcessRequest runProcessRequest, UUIDAndTypeStateKey stateKey) throws QException { Optional optionalProcessState = loadState(stateKey); if(optionalProcessState.isEmpty()) @@ -222,7 +222,7 @@ public class RunProcessAction /******************************************************************************* - ** return true if 'ok', false if error (and time to break loop) + ** Run a single backend step. *******************************************************************************/ private void runBackendStep(RunProcessRequest runProcessRequest, QProcessMetaData process, RunProcessResult runProcessResult, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, ProcessState processState) throws Exception { @@ -231,6 +231,7 @@ public class RunProcessAction runBackendStepRequest.setStepName(backendStep.getName()); runBackendStepRequest.setSession(runProcessRequest.getSession()); runBackendStepRequest.setCallback(runProcessRequest.getCallback()); + runBackendStepRequest.setAsyncJobCallback(runProcessRequest.getAsyncJobCallback()); RunBackendStepResult lastFunctionResult = new RunBackendStepAction().execute(runBackendStepRequest); storeState(stateKey, lastFunctionResult.getProcessState()); @@ -296,6 +297,16 @@ public class RunProcessAction + /******************************************************************************* + ** public method to get a process state just by UUID. + *******************************************************************************/ + public static Optional getState(String processUUID) + { + return (getStateProvider().get(ProcessState.class, new UUIDAndTypeStateKey(UUID.fromString(processUUID), StateType.PROCESS_STATUS))); + } + + + /******************************************************************************* ** Store the process state from a function result to the state provider ** diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobCallback.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobCallback.java index 9db4a7a2..2822f81c 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobCallback.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobCallback.java @@ -90,7 +90,7 @@ public class AsyncJobCallback /******************************************************************************* ** *******************************************************************************/ - private void storeUpdatedStatus() + protected void storeUpdatedStatus() { AsyncJobManager.getStateProvider().put(new UUIDAndTypeStateKey(jobUUID, StateType.ASYNC_JOB_STATUS), asyncJobStatus); } diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobManager.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobManager.java index 27d3b2eb..4e3acc3f 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobManager.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobManager.java @@ -113,7 +113,7 @@ public class AsyncJobManager ** Load an instance of the appropriate state provider ** *******************************************************************************/ - protected static StateProviderInterface getStateProvider() + static StateProviderInterface getStateProvider() { // TODO - read this from somewhere in meta data eh? return InMemoryStateProvider.getInstance(); diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobStatus.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobStatus.java index d3e2dc91..83810512 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobStatus.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobStatus.java @@ -39,6 +39,23 @@ public class AsyncJobStatus implements Serializable + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public String toString() + { + return "AsyncJobStatus{" + + "state=" + state + + ", message='" + message + '\'' + + ", current=" + current + + ", total=" + total + + ", caughtException=" + caughtException + + '}'; + } + + + /******************************************************************************* ** Getter for state ** diff --git a/src/main/java/com/kingsrook/qqq/backend/core/actions/async/JobGoingAsyncException.java b/src/main/java/com/kingsrook/qqq/backend/core/actions/async/JobGoingAsyncException.java index b22fe623..313b4de2 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/actions/async/JobGoingAsyncException.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/actions/async/JobGoingAsyncException.java @@ -23,7 +23,10 @@ package com.kingsrook.qqq.backend.core.actions.async; /******************************************************************************* + ** Exception thrown by AsyncJobManager, not to indicate an error, per se, but + ** rather to indicate that a job has taken too long, as is now "going async". ** + ** So, this exception contains the jobUUID. *******************************************************************************/ public class JobGoingAsyncException extends Exception { diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepRequest.java b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepRequest.java index 7955d83f..90122ea6 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepRequest.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepRequest.java @@ -25,6 +25,9 @@ package com.kingsrook.qqq.backend.core.model.actions.processes; import java.io.Serializable; import java.util.List; import java.util.Map; +import java.util.UUID; +import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback; +import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus; import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback; import com.kingsrook.qqq.backend.core.model.actions.AbstractQRequest; import com.kingsrook.qqq.backend.core.model.data.QRecord; @@ -43,6 +46,7 @@ public class RunBackendStepRequest extends AbstractQRequest private String processName; private String stepName; private QProcessCallback callback; + private AsyncJobCallback asyncJobCallback; @@ -312,4 +316,31 @@ public class RunBackendStepRequest extends AbstractQRequest { return processState; } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void setAsyncJobCallback(AsyncJobCallback asyncJobCallback) + { + this.asyncJobCallback = asyncJobCallback; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public AsyncJobCallback getAsyncJobCallback() + { + if (asyncJobCallback == null) + { + ///////////////////////////////////////////////////////////////////////// + // avoid NPE in case we didn't have one of these! create a new one... // + ///////////////////////////////////////////////////////////////////////// + asyncJobCallback = new AsyncJobCallback(UUID.randomUUID(), new AsyncJobStatus()); + } + return (asyncJobCallback); + } } \ No newline at end of file diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java b/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java index cbb622e3..aa503b4f 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/data/QRecord.java @@ -23,9 +23,11 @@ package com.kingsrook.qqq.backend.core.model.data; import java.io.Serializable; +import java.math.BigDecimal; import java.time.LocalDate; import java.util.LinkedHashMap; import java.util.Map; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; /******************************************************************************* @@ -227,7 +229,7 @@ public class QRecord implements Serializable *******************************************************************************/ public String getValueString(String fieldName) { - return ((String) values.get(fieldName)); + return (ValueUtils.getValueAsString(values.get(fieldName))); } @@ -238,7 +240,28 @@ public class QRecord implements Serializable *******************************************************************************/ public Integer getValueInteger(String fieldName) { - return ((Integer) values.get(fieldName)); + return (ValueUtils.getValueAsInteger(values.get(fieldName))); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public BigDecimal getValueBigDecimal(String fieldName) + { + return (ValueUtils.getValueAsBigDecimal(values.get(fieldName))); + } + + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Boolean getValueBoolean(String fieldName) + { + return (ValueUtils.getValueAsBoolean(values.get(fieldName))); } @@ -249,6 +272,7 @@ public class QRecord implements Serializable *******************************************************************************/ public LocalDate getValueDate(String fieldName) { + // todo - rewrite using ValueUtils... return ((LocalDate) values.get(fieldName)); } @@ -309,6 +333,7 @@ public class QRecord implements Serializable } + /******************************************************************************* ** Get one backendDetail from this record as a String ** diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QFieldMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QFieldMetaData.java index dc8e11ac..36a757c3 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QFieldMetaData.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QFieldMetaData.java @@ -35,7 +35,7 @@ public class QFieldMetaData private String label; private String backendName; private QFieldType type; - private boolean isRequired; + private boolean isRequired = false; private Serializable defaultValue; private String possibleValueSourceName; diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QBackendStepMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QBackendStepMetaData.java index 6e2fdb40..1b0a6df7 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QBackendStepMetaData.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QBackendStepMetaData.java @@ -170,7 +170,7 @@ public class QBackendStepMetaData extends QStepMetaData /******************************************************************************* ** Get a list of all of the input fields used by this function *******************************************************************************/ - @JsonIgnore + @JsonIgnore // because this is a computed property - we don't want it in our json. @Override public List getInputFields() { @@ -187,7 +187,7 @@ public class QBackendStepMetaData extends QStepMetaData /******************************************************************************* ** Get a list of all of the output fields used by this function *******************************************************************************/ - @JsonIgnore + @JsonIgnore // because this is a computed property - we don't want it in our json. @Override public List getOutputFields() { diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java index 6d726089..949fc346 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java @@ -212,7 +212,7 @@ public class QProcessMetaData /******************************************************************************* - ** Wrapper to getStep, that internally casts t0 BackendStepMetaData + ** Wrapper to getStep, that internally casts to BackendStepMetaData *******************************************************************************/ public QBackendStepMetaData getBackendStep(String name) { diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QStepMetaData.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QStepMetaData.java index 42eb90e2..cbfd2c84 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QStepMetaData.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QStepMetaData.java @@ -33,9 +33,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.serialization.QStepMetaData /******************************************************************************* ** Meta-Data to define a step in a process in a QQQ instance. ** + ** Specifically, this is a base-class for QFrontendStepMetaData and QBackendStepMetaData. + ** *******************************************************************************/ @JsonDeserialize(using = QStepMetaDataDeserializer.class) -public class QStepMetaData +public abstract class QStepMetaData { private String name; private String label; diff --git a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/QStepMetaDataDeserializer.java b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/QStepMetaDataDeserializer.java index 4b7608cf..cc3ec609 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/QStepMetaDataDeserializer.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/serialization/QStepMetaDataDeserializer.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; @@ -47,7 +48,7 @@ public class QStepMetaDataDeserializer extends JsonDeserializer Class targetClass = switch(stepType) { case "backend" -> QBackendStepMetaData.class; - case "frontend" -> QBackendStepMetaData.class; + case "frontend" -> QFrontendStepMetaData.class; default -> throw new IllegalArgumentException("Unsupported StepType " + stepType + " for deserialization"); }; return (DeserializerUtils.reflectivelyDeserialize(targetClass, treeNode)); diff --git a/src/main/java/com/kingsrook/qqq/backend/core/state/StateType.java b/src/main/java/com/kingsrook/qqq/backend/core/state/StateType.java index ece3d045..14af21b5 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/state/StateType.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/state/StateType.java @@ -23,7 +23,10 @@ package com.kingsrook.qqq.backend.core.state; /******************************************************************************* + ** Possible types of states to be stored. ** + ** Idea: could these have the corresponding Classes that they support? + ** ala PROCESS_STATUS(ProcessStatus.class) ? *******************************************************************************/ public enum StateType { diff --git a/src/main/java/com/kingsrook/qqq/backend/core/utils/ExceptionUtils.java b/src/main/java/com/kingsrook/qqq/backend/core/utils/ExceptionUtils.java index fcb80418..c4d71714 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/utils/ExceptionUtils.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/utils/ExceptionUtils.java @@ -35,7 +35,7 @@ public class ExceptionUtils /******************************************************************************* ** Find a specific exception class in an exception's caused-by chain. Returns - ** null if not found. Be aware, uses class.isInstaance (so sub-classes get found). + ** null if not found. Be aware, uses class.isInstance (so sub-classes get found). ** *******************************************************************************/ public static T findClassInRootChain(Throwable e, Class targetClass) diff --git a/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java b/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java index 3d1b131a..cc48920c 100644 --- a/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java +++ b/src/main/java/com/kingsrook/qqq/backend/core/utils/ValueUtils.java @@ -33,7 +33,59 @@ public class ValueUtils { /******************************************************************************* - ** + ** Type-safely make a String from any Object. + *******************************************************************************/ + public static String getValueAsString(Object value) + { + if(value == null) + { + return (null); + } + else if(value instanceof String s) + { + return (s); + } + else + { + return (String.valueOf(value)); + } + } + + + + /******************************************************************************* + ** Returns null for null input; + ** Returns the input object for Boolean-typed inputs. + ** Then, follows Boolean.parseBoolean, returning true iff value is a case-insensitive + ** match for "true", for String.valueOf the input + *******************************************************************************/ + public static Boolean getValueAsBoolean(Object value) + { + if(value == null) + { + return (null); + } + else if(value instanceof Boolean b) + { + return (b); + } + else if(value instanceof String s) + { + return (Boolean.parseBoolean(s)); + } + else + { + return (Boolean.parseBoolean(String.valueOf(value))); + } + } + + + + /******************************************************************************* + ** Type-safely make an Integer from any Object. + ** null and empty-string inputs return null. + ** We try to strip away commas and decimals (as long as they are exactly equal to the int value) + ** We may throw if the input can't be converted to an integer. *******************************************************************************/ public static Integer getValueAsInteger(Object value) throws QValueException { @@ -53,7 +105,7 @@ public class ValueUtils } else if(value instanceof Float f) { - if (f.intValue() != f) + if(f.intValue() != f) { throw (new QValueException(f + " does not have an exact integer representation.")); } @@ -61,7 +113,7 @@ public class ValueUtils } else if(value instanceof Double d) { - if (d.intValue() != d) + if(d.intValue() != d) { throw (new QValueException(d + " does not have an exact integer representation.")); } @@ -122,4 +174,78 @@ public class ValueUtils } } + + + /******************************************************************************* + ** Type-safely make a BigDecimal from any Object. + ** null and empty-string inputs return null. + ** We may throw if the input can't be converted to a BigDecimal + *******************************************************************************/ + public static BigDecimal getValueAsBigDecimal(Object value) throws QValueException + { + try + { + if(value == null) + { + return (null); + } + else if(value instanceof BigDecimal bd) + { + return (bd); + } + else if(value instanceof Integer i) + { + return new BigDecimal(i); + } + else if(value instanceof Long l) + { + return new BigDecimal(l); + } + else if(value instanceof Float f) + { + return new BigDecimal(f); + } + else if(value instanceof Double d) + { + return new BigDecimal(d); + } + else if(value instanceof String s) + { + if(!StringUtils.hasContent(s)) + { + return (null); + } + + try + { + return (new BigDecimal(s)); + } + catch(NumberFormatException nfe) + { + if(s.contains(",")) + { + String sWithoutCommas = s.replaceAll(",", ""); + try + { + return (getValueAsBigDecimal(sWithoutCommas)); + } + catch(Exception ignore) + { + throw (nfe); + } + } + throw (nfe); + } + } + else + { + throw (new IllegalArgumentException("Unsupported class " + value.getClass().getName() + " for converting to BigDecimal.")); + } + } + catch(Exception e) + { + throw (new QValueException("Value [" + value + "] could not be converted to an BigDecimal.", e)); + } + } + } diff --git a/src/test/java/com/kingsrook/qqq/backend/core/actions/ProcessMetaDataActionTest.java b/src/test/java/com/kingsrook/qqq/backend/core/actions/ProcessMetaDataActionTest.java index 0fb620ae..1de011da 100644 --- a/src/test/java/com/kingsrook/qqq/backend/core/actions/ProcessMetaDataActionTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/core/actions/ProcessMetaDataActionTest.java @@ -23,7 +23,7 @@ package com.kingsrook.qqq.backend.core.actions; import com.kingsrook.qqq.backend.core.exceptions.QException; -import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; +import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException; import com.kingsrook.qqq.backend.core.model.actions.metadata.process.ProcessMetaDataRequest; import com.kingsrook.qqq.backend.core.model.actions.metadata.process.ProcessMetaDataResult; import com.kingsrook.qqq.backend.core.utils.TestUtils; @@ -67,7 +67,7 @@ class ProcessMetaDataActionTest @Test public void test_notFound() { - assertThrows(QUserFacingException.class, () -> { + assertThrows(QNotFoundException.class, () -> { ProcessMetaDataRequest request = new ProcessMetaDataRequest(TestUtils.defineInstance()); request.setSession(TestUtils.getMockSession()); request.setProcessName("willNotBeFound"); diff --git a/src/test/java/com/kingsrook/qqq/backend/core/actions/RunProcessTest.java b/src/test/java/com/kingsrook/qqq/backend/core/actions/RunProcessTest.java index c20907ed..fa290c0b 100644 --- a/src/test/java/com/kingsrook/qqq/backend/core/actions/RunProcessTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/core/actions/RunProcessTest.java @@ -27,21 +27,27 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.interfaces.mock.MockBackendStep; +import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState; import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest; import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult; import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; +import com.kingsrook.qqq.backend.core.state.StateType; +import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey; import com.kingsrook.qqq.backend.core.utils.TestUtils; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -99,6 +105,7 @@ public class RunProcessTest Optional breakingAtStep0 = result0.getProcessState().getNextStepName(); assertTrue(breakingAtStep0.isPresent()); assertInstanceOf(QFrontendStepMetaData.class, instance.getProcessStep(processName, breakingAtStep0.get())); + assertNull(result0.getValues().get(MockBackendStep.FIELD_MOCK_VALUE)); ////////////////////////////////////////////// // now run again, proceeding from this step // @@ -113,6 +120,7 @@ public class RunProcessTest assertTrue(breakingAtStep1.isPresent()); assertInstanceOf(QFrontendStepMetaData.class, instance.getProcessStep(processName, breakingAtStep1.get())); assertNotEquals(breakingAtStep0.get(), breakingAtStep1.get()); + assertEquals(MockBackendStep.MOCK_VALUE, result1.getValues().get(MockBackendStep.FIELD_MOCK_VALUE)); } @@ -136,6 +144,7 @@ public class RunProcessTest RunProcessResult runProcessResult = new RunProcessAction().execute(request); assertTrue(runProcessResult.getException().isEmpty()); assertEquals(MockBackendStep.MOCK_VALUE, runProcessResult.getValues().get(MockBackendStep.FIELD_MOCK_VALUE)); + assertTrue(runProcessResult.getProcessState().getNextStepName().isEmpty()); } @@ -169,6 +178,79 @@ public class RunProcessTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testPrimeProcessRequestNewProcess() throws QException + { + //////////////////////////////////////////////////////////////////////////////// + // this is a flow where it's a new process - so, we should create a new state // + //////////////////////////////////////////////////////////////////////////////// + RunProcessRequest runProcessRequest = new RunProcessRequest(); + UUIDAndTypeStateKey stateKey = new UUIDAndTypeStateKey(UUID.randomUUID(), StateType.PROCESS_STATUS); + ProcessState processState = new RunProcessAction().primeProcessState(runProcessRequest, stateKey); + assertNotNull(processState); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testPrimeProcessRequestAttemptToContinueButStateNotFound() throws QException + { + //////////////////////////////////////////////////////////////////////////////////////////////// + // this is a flow where it's a continue, but we don't have a state stored, so it should throw // + //////////////////////////////////////////////////////////////////////////////////////////////// + RunProcessRequest runProcessRequest = new RunProcessRequest(); + runProcessRequest.setStartAfterStep("setupStep"); + UUIDAndTypeStateKey stateKey = new UUIDAndTypeStateKey(UUID.randomUUID(), StateType.PROCESS_STATUS); + + assertThrows(QException.class, () -> + { + new RunProcessAction().primeProcessState(runProcessRequest, stateKey); + }); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testPrimeProcessRequestAttemptToContinueAndStateIsFound() throws QException + { + //////////////////////////////////////////////////////////////////////////////////////////////// + // this is a flow where it's a continue, but we don't have a state stored, so it should throw // + //////////////////////////////////////////////////////////////////////////////////////////////// + RunProcessRequest runProcessRequest = new RunProcessRequest(); + runProcessRequest.setStartAfterStep("setupStep"); + runProcessRequest.addValue("foo", "bar"); + runProcessRequest.addValue("alpha", "beta"); + UUIDAndTypeStateKey stateKey = new UUIDAndTypeStateKey(UUID.randomUUID(), StateType.PROCESS_STATUS); + + //////////////////////////////////////////////// + // simulate the state being previously stored // + //////////////////////////////////////////////// + ProcessState oldProcessState = new ProcessState(); + oldProcessState.getValues().put("key", "myValue"); + oldProcessState.getValues().put("foo", "fubu"); + RunProcessAction.getStateProvider().put(stateKey, oldProcessState); + + ProcessState primedProcessState = new RunProcessAction().primeProcessState(runProcessRequest, stateKey); + assertEquals("myValue", primedProcessState.getValues().get("key")); + + ///////////////////////////////////////////////////////////////////////////////////////////// + // make sure values that were in the original request trump values that had been in state. // + ///////////////////////////////////////////////////////////////////////////////////////////// + assertEquals("bar", primedProcessState.getValues().get("foo")); + assertEquals("beta", primedProcessState.getValues().get("alpha")); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/src/test/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobManagerTest.java b/src/test/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobManagerTest.java index 2b82559a..276f0b31 100644 --- a/src/test/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobManagerTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/core/actions/async/AsyncJobManagerTest.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; /******************************************************************************* @@ -65,7 +66,7 @@ class AsyncJobManagerTest assertThrows(JobGoingAsyncException.class, () -> { AsyncJobManager asyncJobManager = new AsyncJobManager(); - Integer answer = asyncJobManager.startJob(1, TimeUnit.MICROSECONDS, (callback) -> + asyncJobManager.startJob(1, TimeUnit.MICROSECONDS, (callback) -> { Thread.sleep(1_000); return (ANSWER); @@ -108,6 +109,7 @@ class AsyncJobManagerTest Thread.sleep(50); throw (new IllegalArgumentException(message)); }); + fail("We should catch a JobGoingAsyncException"); } catch(JobGoingAsyncException jgae) { @@ -140,6 +142,7 @@ class AsyncJobManagerTest callback.updateStatus(postMessage, 1, 1); return (ANSWER); }); + fail("We should catch a JobGoingAsyncException"); } catch(JobGoingAsyncException jgae) { @@ -152,6 +155,7 @@ class AsyncJobManagerTest assertEquals(1, jobStatus.get().getTotal()); Thread.sleep(200); + jobStatus = asyncJobManager.getJobStatus(jgae.getJobUUID()); assertEquals(AsyncJobState.COMPLETE, jobStatus.get().getState()); assertEquals(postMessage, jobStatus.get().getMessage()); assertEquals(1, jobStatus.get().getCurrent()); diff --git a/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLProcessTest.java b/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLProcessTest.java index 3c472876..0ea958e8 100644 --- a/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLProcessTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/basic/BasicETLProcessTest.java @@ -56,6 +56,7 @@ class BasicETLProcessTest RunProcessResult result = new RunProcessAction().execute(request); assertNotNull(result); assertTrue(result.getRecords().stream().allMatch(r -> r.getValues().containsKey("id")), "records should have an id, set by the process"); + assertTrue(result.getException().isEmpty()); } @@ -82,6 +83,7 @@ class BasicETLProcessTest RunProcessResult result = new RunProcessAction().execute(request); assertNotNull(result); assertTrue(result.getRecords().stream().allMatch(r -> r.getValues().containsKey("id")), "records should have an id, set by the process"); + assertTrue(result.getException().isEmpty()); } } \ No newline at end of file diff --git a/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java b/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java index 3c758dfa..9508d8fd 100644 --- a/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/core/utils/CollectionUtilsTest.java @@ -415,6 +415,9 @@ class CollectionUtilsTest + /******************************************************************************* + ** + *******************************************************************************/ @Test void test_safelyGetPage() { @@ -494,4 +497,5 @@ class CollectionUtilsTest assertEquals(4, pageCount); assertEquals(list, accumulator); } + } diff --git a/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java b/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java index 7d27cc46..a422338a 100644 --- a/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/core/utils/ValueUtilsTest.java @@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.utils; import java.math.BigDecimal; +import java.math.MathContext; import com.kingsrook.qqq.backend.core.exceptions.QValueException; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -34,6 +35,51 @@ import static org.junit.jupiter.api.Assertions.*; class ValueUtilsTest { + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testGetValueAsString() throws QValueException + { + assertNull(ValueUtils.getValueAsString(null)); + assertEquals("", ValueUtils.getValueAsString("")); + assertEquals(" ", ValueUtils.getValueAsString(" ")); + assertEquals("A", ValueUtils.getValueAsString("A")); + assertEquals("1", ValueUtils.getValueAsString("1")); + assertEquals("1", ValueUtils.getValueAsString(1)); + assertEquals("1", ValueUtils.getValueAsString(1)); + assertEquals("1.10", ValueUtils.getValueAsString(new BigDecimal("1.10"))); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testGetValueAsBoolean() throws QValueException + { + assertNull(ValueUtils.getValueAsBoolean(null)); + assertTrue(ValueUtils.getValueAsBoolean("true")); + assertTrue(ValueUtils.getValueAsBoolean("True")); + assertTrue(ValueUtils.getValueAsBoolean("TRUE")); + assertFalse(ValueUtils.getValueAsBoolean("false")); + assertFalse(ValueUtils.getValueAsBoolean("yes")); + assertFalse(ValueUtils.getValueAsBoolean("t")); + assertFalse(ValueUtils.getValueAsBoolean(new Object())); + assertFalse(ValueUtils.getValueAsBoolean(1)); + assertTrue(ValueUtils.getValueAsBoolean(new Object() + { + @Override + public String toString() + { + return ("true"); + } + })); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -63,4 +109,35 @@ class ValueUtilsTest assertThrows(QValueException.class, () -> ValueUtils.getValueAsInteger(1.1D)); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testGetValueAsBigDecimal() throws QValueException + { + assertNull(ValueUtils.getValueAsBigDecimal(null)); + assertNull(ValueUtils.getValueAsBigDecimal("")); + assertNull(ValueUtils.getValueAsBigDecimal(" ")); + assertEquals(new BigDecimal("1"), ValueUtils.getValueAsBigDecimal(1)); + assertEquals(new BigDecimal("1"), ValueUtils.getValueAsBigDecimal("1")); + assertEquals(new BigDecimal("1000"), ValueUtils.getValueAsBigDecimal("1,000")); + assertEquals(new BigDecimal("1000000"), ValueUtils.getValueAsBigDecimal("1,000,000")); + assertEquals(new BigDecimal("1"), ValueUtils.getValueAsBigDecimal(new BigDecimal(1))); + assertEquals(new BigDecimal("1.00"), ValueUtils.getValueAsBigDecimal(new BigDecimal("1.00"))); + assertEquals(new BigDecimal("-1.00"), ValueUtils.getValueAsBigDecimal("-1.00")); + assertEquals(new BigDecimal("1000.00"), ValueUtils.getValueAsBigDecimal("1,000.00")); + assertEquals(new BigDecimal("1000"), ValueUtils.getValueAsBigDecimal(1000L)); + assertEquals(new BigDecimal("1"), ValueUtils.getValueAsBigDecimal(1.0F)); + assertEquals(new BigDecimal("1"), ValueUtils.getValueAsBigDecimal(1.0D)); + assertEquals(new BigDecimal("1000000000000"), ValueUtils.getValueAsBigDecimal(1_000_000_000_000L)); + assertEquals(0, new BigDecimal("1.1").compareTo(ValueUtils.getValueAsBigDecimal(1.1F).round(MathContext.DECIMAL32))); + assertEquals(0, new BigDecimal("1.1").compareTo(ValueUtils.getValueAsBigDecimal(1.1D).round(MathContext.DECIMAL64))); + + assertThrows(QValueException.class, () -> ValueUtils.getValueAsBigDecimal("a")); + assertThrows(QValueException.class, () -> ValueUtils.getValueAsBigDecimal("a,b")); + assertThrows(QValueException.class, () -> ValueUtils.getValueAsBigDecimal(new Object())); + } + } \ No newline at end of file