From e10a1e40da6f5d0dab7a9f91130913145d4e678f Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 17 May 2024 16:16:26 -0500 Subject: [PATCH] Implement min/max records enforcement --- .../processes/RunBackendStepAction.java | 42 ++++++++- .../processes/RunBackendStepActionTest.java | 92 ++++++++++++++++--- 2 files changed, 116 insertions(+), 18 deletions(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunBackendStepAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunBackendStepAction.java index fc23904d..ba9afdbd 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunBackendStepAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunBackendStepAction.java @@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; 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.metadata.code.QCodeReference; @@ -82,7 +83,7 @@ public class RunBackendStepAction ////////////////////////////////////////////////////////////////////////////////////// // ensure input data is set as needed - use callback object to get anything missing // ////////////////////////////////////////////////////////////////////////////////////// - ensureRecordsAreInRequest(runBackendStepInput, backendStepMetaData); + ensureRecordsAreInRequest(runBackendStepInput, backendStepMetaData, process); ensureInputFieldsAreInRequest(runBackendStepInput, backendStepMetaData); //////////////////////////////////////////////////////////////////// @@ -167,7 +168,7 @@ public class RunBackendStepAction ** check if this step uses a record list - and if so, if we need to get one ** via the callback *******************************************************************************/ - private void ensureRecordsAreInRequest(RunBackendStepInput runBackendStepInput, QBackendStepMetaData step) throws QException + private void ensureRecordsAreInRequest(RunBackendStepInput runBackendStepInput, QBackendStepMetaData step, QProcessMetaData process) throws QException { QFunctionInputMetaData inputMetaData = step.getInputMetaData(); if(inputMetaData != null && inputMetaData.getRecordListMetaData() != null) @@ -190,9 +191,44 @@ public class RunBackendStepAction queryInput.setFilter(callback.getQueryFilter()); + ////////////////////////////////////////////////////////////////////////////////////////// + // if process has a max-no of records, set a limit on the process of that number plus 1 // + // (the plus 1 being so we can see "oh, you selected more than that many; error!" // + ////////////////////////////////////////////////////////////////////////////////////////// + if(process.getMaxInputRecords() != null) + { + if(callback.getQueryFilter() == null) + { + queryInput.setFilter(new QQueryFilter()); + } + + queryInput.getFilter().setLimit(process.getMaxInputRecords() + 1); + } + QueryOutput queryOutput = new QueryAction().execute(queryInput); runBackendStepInput.setRecords(queryOutput.getRecords()); - // todo - handle 0 results found? + + //////////////////////////////////////////////////////////////////////////////// + // if process defines a max, and more than the max were found, throw an error // + //////////////////////////////////////////////////////////////////////////////// + if(process.getMaxInputRecords() != null) + { + if(queryOutput.getRecords().size() > process.getMaxInputRecords()) + { + throw (new QUserFacingException("Too many records were selected for this process. At most, only " + process.getMaxInputRecords() + " can be selected.")); + } + } + + ///////////////////////////////////////////////////////////////////////////////// + // if process defines a min, and fewer than the min were found, throw an error // + ///////////////////////////////////////////////////////////////////////////////// + if(process.getMinInputRecords() != null) + { + if(queryOutput.getRecords().size() < process.getMinInputRecords()) + { + throw (new QUserFacingException("Too few records were selected for this process. At least " + process.getMinInputRecords() + " must be selected.")); + } + } } } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/processes/RunBackendStepActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/processes/RunBackendStepActionTest.java index 839172d8..0caad01f 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/processes/RunBackendStepActionTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/processes/RunBackendStepActionTest.java @@ -27,13 +27,21 @@ import java.math.BigDecimal; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.utils.TestUtils; 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.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -53,7 +61,7 @@ public class RunBackendStepActionTest extends BaseTest { TestCallback callback = new TestCallback(); RunBackendStepInput request = new RunBackendStepInput(); - request.setProcessName("greet"); + request.setProcessName(TestUtils.PROCESS_NAME_GREET_PEOPLE); request.setStepName("prepare"); request.setCallback(callback); RunBackendStepOutput result = new RunBackendStepAction().execute(request); @@ -67,6 +75,60 @@ public class RunBackendStepActionTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testMinMaxInputRecords() throws QException + { + //////////////////////////////////////////// + // put a min-input-records on the process // + //////////////////////////////////////////// + QContext.getQInstance().getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE).withMinInputRecords(5); + + ////////////////////////////////////////////////////////////////////////////////////// + // insert fewer than that min - then run w/ non-filtered filter, and assert we fail // + ////////////////////////////////////////////////////////////////////////////////////// + for(int i = 0; i < 3; i++) + { + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord().withValue("firstName", String.valueOf(i)))); + } + + Supplier inputSupplier = () -> + { + RunBackendStepInput input = new RunBackendStepInput(); + input.setProcessName(TestUtils.PROCESS_NAME_GREET_PEOPLE); + input.setStepName("prepare"); + input.setCallback(QProcessCallbackFactory.forFilter(new QQueryFilter())); + return (input); + }; + + assertThatThrownBy(() -> new RunBackendStepAction().execute(inputSupplier.get())) + .isInstanceOf(QUserFacingException.class) + .hasMessageContaining("Too few records"); + + //////////////////////////////////////////////////// + // insert a few more - and then it should succeed // + //////////////////////////////////////////////////// + for(int i = 3; i < 10; i++) + { + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord().withValue("firstName", String.valueOf(i)))); + } + + new RunBackendStepAction().execute(inputSupplier.get()); + + //////////////////////////////////////////////////////////// + // now put a max on the process, and it should fail again // + //////////////////////////////////////////////////////////// + QContext.getQInstance().getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE).withMaxInputRecords(8); + + assertThatThrownBy(() -> new RunBackendStepAction().execute(inputSupplier.get())) + .isInstanceOf(QUserFacingException.class) + .hasMessageContaining("Too many records"); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -100,20 +162,20 @@ public class RunBackendStepActionTest extends BaseTest for(QFieldMetaData field : fields) { rs.put(field.getName(), switch(field.getType()) - { - case STRING -> "ABC"; - case INTEGER -> 42; - case LONG -> 42L; - case DECIMAL -> new BigDecimal("47"); - case BOOLEAN -> true; - case DATE, TIME, DATE_TIME -> null; - case TEXT -> """ - ABC - XYZ"""; - case HTML -> "Oh my"; - case PASSWORD -> "myPa**word"; - case BLOB -> new byte[] { 1, 2, 3, 4 }; - }); + { + case STRING -> "ABC"; + case INTEGER -> 42; + case LONG -> 42L; + case DECIMAL -> new BigDecimal("47"); + case BOOLEAN -> true; + case DATE, TIME, DATE_TIME -> null; + case TEXT -> """ + ABC + XYZ"""; + case HTML -> "Oh my"; + case PASSWORD -> "myPa**word"; + case BLOB -> new byte[] { 1, 2, 3, 4 }; + }); } return (rs); }