mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
CE-847 New test on automation status runner - for where a child table has a post-insert that updates the parent!
This commit is contained in:
@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. 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.automation.polling;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostInsertCustomizer;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.AggregateAction;
|
||||||
|
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.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.aggregate.Aggregate;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOperator;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateResult;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.GroupBy;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||||
|
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.QCriteriaOperator;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static com.kingsrook.qqq.backend.core.actions.automation.polling.PollingAutomationPerTableRunnerTest.runAllTableActions;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Test for the case where:
|
||||||
|
** - inserting into a main table and a child table, and the child table has a
|
||||||
|
** post-insert customizer, which mo
|
||||||
|
*******************************************************************************/
|
||||||
|
public class PollingAutomationPerTableRunnerChildPostInsertCustomizerTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void test() throws QException
|
||||||
|
{
|
||||||
|
////////////////////////////////////
|
||||||
|
// add automations to order table //
|
||||||
|
////////////////////////////////////
|
||||||
|
QContext.getQInstance().getTable(TestUtils.TABLE_NAME_ORDER)
|
||||||
|
.withField(TestUtils.standardQqqAutomationStatusField())
|
||||||
|
.withAutomationDetails(TestUtils.defineStandardAutomationDetails()
|
||||||
|
.withAction(new TableAutomationAction()
|
||||||
|
.withName("orderPostInsertAction")
|
||||||
|
.withTriggerEvent(TriggerEvent.POST_INSERT)
|
||||||
|
.withCodeReference(new QCodeReference(OrderPostInsertAction.class))
|
||||||
|
));
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// add a post-insert customizer to line-ite table (child of order table) //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
QContext.getQInstance().getTable(TestUtils.TABLE_NAME_LINE_ITEM)
|
||||||
|
.withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(LineItemPostInsertCustomizer.class));
|
||||||
|
|
||||||
|
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
|
||||||
|
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
insertInput.setRecords(List.of(new QRecord()
|
||||||
|
.withValue("orderNo", "10101")
|
||||||
|
.withAssociatedRecord("orderLine", new QRecord()
|
||||||
|
.withValue("sku", "ABC")
|
||||||
|
.withValue("quantity", 1))));
|
||||||
|
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// make sure the order is in pending-inserts status (at one time, a bug meant that it wouldn't have been...) //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
QRecord order = new GetAction().executeForRecord(new GetInput(TestUtils.TABLE_NAME_ORDER).withPrimaryKey(1));
|
||||||
|
assertEquals(AutomationStatus.PENDING_INSERT_AUTOMATIONS.getId(), order.getValue(TestUtils.standardQqqAutomationStatusField().getName()));
|
||||||
|
assertEquals(new BigDecimal(1), order.getValueBigDecimal("total"));
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// run automations - that should... insert a second line item, but should leave the order in //
|
||||||
|
// automation-status = OK, to avoid perpetual re-running //
|
||||||
|
// the line-item post-inserter should run a second time, making the order's total = 2 //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
runAllTableActions(QContext.getQInstance());
|
||||||
|
|
||||||
|
{
|
||||||
|
QRecord order = new GetAction().executeForRecord(new GetInput(TestUtils.TABLE_NAME_ORDER).withPrimaryKey(1));
|
||||||
|
assertEquals(AutomationStatus.OK.getId(), order.getValue(TestUtils.standardQqqAutomationStatusField().getName()));
|
||||||
|
assertEquals(new BigDecimal(2), order.getValueBigDecimal("total"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class OrderPostInsertAction extends RecordAutomationHandler
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void execute(RecordAutomationInput recordAutomationInput) throws QException
|
||||||
|
{
|
||||||
|
///////////////////////////////////////
|
||||||
|
// add a new line item to the orders //
|
||||||
|
///////////////////////////////////////
|
||||||
|
List<QRecord> lineItemsToInsert = new ArrayList<>();
|
||||||
|
for(QRecord record : recordAutomationInput.getRecordList())
|
||||||
|
{
|
||||||
|
lineItemsToInsert.add(new QRecord()
|
||||||
|
.withValue("orderId", record.getValue("id"))
|
||||||
|
.withValue("sku", UUID.randomUUID())
|
||||||
|
.withValue("quantity", 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_LINE_ITEM).withRecords(lineItemsToInsert));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class LineItemPostInsertCustomizer extends AbstractPostInsertCustomizer
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public List<QRecord> apply(List<QRecord> records) throws QException
|
||||||
|
{
|
||||||
|
//////////////////////////////////
|
||||||
|
// count line items by order id //
|
||||||
|
//////////////////////////////////
|
||||||
|
Set<Serializable> orderIds = records.stream().map(r -> r.getValue("orderId")).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
GroupBy groupByOrderId = new GroupBy(QFieldType.STRING, "orderId");
|
||||||
|
Aggregate countId = new Aggregate("id", AggregateOperator.COUNT);
|
||||||
|
|
||||||
|
AggregateInput aggregateInput = new AggregateInput();
|
||||||
|
aggregateInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
|
||||||
|
aggregateInput.setFilter(new QQueryFilter(new QFilterCriteria("orderId", QCriteriaOperator.IN, orderIds)));
|
||||||
|
aggregateInput.withGroupBy(groupByOrderId);
|
||||||
|
aggregateInput.withAggregate(countId);
|
||||||
|
AggregateOutput aggregateOutput = new AggregateAction().execute(aggregateInput);
|
||||||
|
Map<Integer, Integer> countByOrderId = new HashMap<>();
|
||||||
|
for(AggregateResult result : aggregateOutput.getResults())
|
||||||
|
{
|
||||||
|
countByOrderId.put(ValueUtils.getValueAsInteger(result.getGroupByValue(groupByOrderId)), ValueUtils.getValueAsInteger(result.getAggregateValue(countId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// update the order total fields //
|
||||||
|
// s/b in bulk, but, meh //
|
||||||
|
///////////////////////////////////
|
||||||
|
for(Integer orderId : countByOrderId.keySet())
|
||||||
|
{
|
||||||
|
UpdateInput updateInput = new UpdateInput();
|
||||||
|
updateInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
updateInput.setRecords(List.of(new QRecord()
|
||||||
|
.withValue("id", orderId)
|
||||||
|
.withValue("total", new BigDecimal(countByOrderId.get(orderId)))));
|
||||||
|
new UpdateAction().execute(updateInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (records);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -56,14 +56,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAut
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
|
|
||||||
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.processes.implementations.etl.streamedwithfrontend.LoadViaInsertStep;
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaInsertStep;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcessTest;
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcessTest;
|
||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
@ -77,18 +74,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||||||
class PollingAutomationPerTableRunnerTest extends BaseTest
|
class PollingAutomationPerTableRunnerTest extends BaseTest
|
||||||
{
|
{
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@BeforeEach
|
|
||||||
@AfterEach
|
|
||||||
void beforeAndAfterEach()
|
|
||||||
{
|
|
||||||
MemoryRecordStore.getInstance().reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Test a cycle that does an insert, some automations, then and an update, and more automations.
|
** Test a cycle that does an insert, some automations, then and an update, and more automations.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -200,7 +185,7 @@ class PollingAutomationPerTableRunnerTest extends BaseTest
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void runAllTableActions(QInstance qInstance) throws QException
|
static void runAllTableActions(QInstance qInstance) throws QException
|
||||||
{
|
{
|
||||||
List<PollingAutomationPerTableRunner.TableActionsInterface> tableActions = PollingAutomationPerTableRunner.getTableActions(qInstance, TestUtils.POLLING_AUTOMATION);
|
List<PollingAutomationPerTableRunner.TableActionsInterface> tableActions = PollingAutomationPerTableRunner.getTableActions(qInstance, TestUtils.POLLING_AUTOMATION);
|
||||||
for(PollingAutomationPerTableRunner.TableActionsInterface tableAction : tableActions)
|
for(PollingAutomationPerTableRunner.TableActionsInterface tableAction : tableActions)
|
||||||
|
@ -720,7 +720,7 @@ public class TestUtils
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static QTableAutomationDetails defineStandardAutomationDetails()
|
public static QTableAutomationDetails defineStandardAutomationDetails()
|
||||||
{
|
{
|
||||||
return (new QTableAutomationDetails()
|
return (new QTableAutomationDetails()
|
||||||
.withProviderName(POLLING_AUTOMATION)
|
.withProviderName(POLLING_AUTOMATION)
|
||||||
|
Reference in New Issue
Block a user