Add business loop validation and coverage audit
This commit is contained in:
@@ -0,0 +1,257 @@
|
|||||||
|
package com.ruoyi.generator.service.front;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopAuditResult;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopAction;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopQuantityEffect;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopRecordEffect;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopStateMachine;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.DatabaseColumnDesign;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class BusinessLoopCoverageValidator
|
||||||
|
{
|
||||||
|
public BusinessLoopAuditResult auditDatabase(BusinessLoopPlan plan, DatabaseDesignResponse database)
|
||||||
|
{
|
||||||
|
BusinessLoopAuditResult result = newResult(plan);
|
||||||
|
Map<String, Set<String>> columnsByTable = collectColumnsByTable(database);
|
||||||
|
if (plan == null)
|
||||||
|
{
|
||||||
|
result.getErrors().add("Business loop plan is required");
|
||||||
|
finish(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (BusinessLoopStateMachine stateMachine : safe(plan.getStateMachines()))
|
||||||
|
{
|
||||||
|
if (stateMachine == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!hasColumn(columnsByTable, stateMachine.getTableName(), stateMachine.getStatusField()))
|
||||||
|
{
|
||||||
|
result.getErrors().add("Missing state status field: "
|
||||||
|
+ formatField(stateMachine.getTableName(), stateMachine.getStatusField()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (BusinessLoopQuantityEffect quantityRule : safe(plan.getQuantityRules()))
|
||||||
|
{
|
||||||
|
if (quantityRule == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!hasColumn(columnsByTable, quantityRule.getTableName(), quantityRule.getQuantityField()))
|
||||||
|
{
|
||||||
|
result.getErrors().add("Missing quantity field: "
|
||||||
|
+ formatField(quantityRule.getTableName(), quantityRule.getQuantityField()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Set<String> tableNames = columnsByTable.keySet();
|
||||||
|
for (BusinessLoopRecordEffect recordRule : safe(plan.getRecordRules()))
|
||||||
|
{
|
||||||
|
if (recordRule == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!tableNames.contains(normalize(recordRule.getTableName())))
|
||||||
|
{
|
||||||
|
result.getErrors().add("Missing record table: " + StringUtils.defaultString(recordRule.getTableName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finish(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessLoopAuditResult auditBusinessActions(BusinessLoopPlan plan, List<BusinessActionDesign> actions)
|
||||||
|
{
|
||||||
|
BusinessLoopAuditResult result = newResult(plan);
|
||||||
|
Map<String, BusinessActionDesign> actionsByCode = collectActionsByCode(actions);
|
||||||
|
if (plan == null)
|
||||||
|
{
|
||||||
|
result.getErrors().add("Business loop plan is required");
|
||||||
|
finish(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (BusinessLoopAction planAction : safe(plan.getActions()))
|
||||||
|
{
|
||||||
|
if (planAction == null || !planAction.isRequired())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BusinessActionDesign action = actionsByCode.get(planAction.getCode());
|
||||||
|
if (action == null)
|
||||||
|
{
|
||||||
|
result.getMissingRequiredActions().add(planAction.getCode());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
validateRequiredEffects(result, planAction, action);
|
||||||
|
}
|
||||||
|
finish(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateRequiredEffects(BusinessLoopAuditResult result, BusinessLoopAction planAction,
|
||||||
|
BusinessActionDesign action)
|
||||||
|
{
|
||||||
|
if (size(planAction.getStateTransitions()) > 0 && !hasAnyEffect(action, "SET_STATUS", "UPDATE_FIELDS", "UPDATE_FIELD"))
|
||||||
|
{
|
||||||
|
result.getErrors().add("Missing status effect for required action: " + planAction.getCode());
|
||||||
|
}
|
||||||
|
if (size(planAction.getQuantityEffects()) > 0 && !hasAnyEffect(action, "INCREASE_NUMBER", "DECREASE_NUMBER"))
|
||||||
|
{
|
||||||
|
result.getErrors().add("Missing quantity effect for required action: " + planAction.getCode());
|
||||||
|
}
|
||||||
|
if (size(planAction.getRecordEffects()) > 0 && !hasAnyEffect(action, "INSERT_ROW", "UPDATE_FIELDS"))
|
||||||
|
{
|
||||||
|
result.getErrors().add("Missing record effect for required action: " + planAction.getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasAnyEffect(BusinessActionDesign action, String... types)
|
||||||
|
{
|
||||||
|
if (action == null || action.getEffects() == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Set<String> expectedTypes = new HashSet<String>();
|
||||||
|
for (String type : types)
|
||||||
|
{
|
||||||
|
expectedTypes.add(type);
|
||||||
|
}
|
||||||
|
for (BusinessActionEffectDesign effect : action.getEffects())
|
||||||
|
{
|
||||||
|
if (effect == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String type = StringUtils.defaultString(effect.getType()).toUpperCase(Locale.ENGLISH);
|
||||||
|
if (expectedTypes.contains(type))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, BusinessActionDesign> collectActionsByCode(List<BusinessActionDesign> actions)
|
||||||
|
{
|
||||||
|
Map<String, BusinessActionDesign> actionsByCode = new HashMap<String, BusinessActionDesign>();
|
||||||
|
if (actions == null)
|
||||||
|
{
|
||||||
|
return actionsByCode;
|
||||||
|
}
|
||||||
|
for (BusinessActionDesign action : actions)
|
||||||
|
{
|
||||||
|
if (action != null && StringUtils.isNotBlank(action.getCode()))
|
||||||
|
{
|
||||||
|
actionsByCode.put(action.getCode(), action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actionsByCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Set<String>> collectColumnsByTable(DatabaseDesignResponse database)
|
||||||
|
{
|
||||||
|
Map<String, Set<String>> columnsByTable = new HashMap<String, Set<String>>();
|
||||||
|
if (database == null || database.getTables() == null)
|
||||||
|
{
|
||||||
|
return columnsByTable;
|
||||||
|
}
|
||||||
|
for (DatabaseTableDesign table : database.getTables())
|
||||||
|
{
|
||||||
|
if (table == null || StringUtils.isBlank(table.getTableName()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Set<String> columns = new HashSet<String>();
|
||||||
|
if (table.getColumns() != null)
|
||||||
|
{
|
||||||
|
for (DatabaseColumnDesign column : table.getColumns())
|
||||||
|
{
|
||||||
|
if (column != null && StringUtils.isNotBlank(column.getColumnName()))
|
||||||
|
{
|
||||||
|
columns.add(normalize(column.getColumnName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
columnsByTable.put(normalize(table.getTableName()), columns);
|
||||||
|
}
|
||||||
|
return columnsByTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasColumn(Map<String, Set<String>> columnsByTable, String tableName, String columnName)
|
||||||
|
{
|
||||||
|
Set<String> columns = columnsByTable.get(normalize(tableName));
|
||||||
|
return columns != null && columns.contains(normalize(columnName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatField(String tableName, String fieldName)
|
||||||
|
{
|
||||||
|
return StringUtils.defaultString(tableName) + "." + StringUtils.defaultString(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopAuditResult newResult(BusinessLoopPlan plan)
|
||||||
|
{
|
||||||
|
BusinessLoopAuditResult result = new BusinessLoopAuditResult();
|
||||||
|
result.setLoopActions(size(plan == null ? null : plan.getActions()));
|
||||||
|
result.setStateTransitions(countStateTransitions(plan));
|
||||||
|
result.setQuantityRules(size(plan == null ? null : plan.getQuantityRules()));
|
||||||
|
result.setRecordRules(size(plan == null ? null : plan.getRecordRules()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finish(BusinessLoopAuditResult result)
|
||||||
|
{
|
||||||
|
result.setComplete(result.getErrors().isEmpty() && result.getMissingRequiredActions().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countStateTransitions(BusinessLoopPlan plan)
|
||||||
|
{
|
||||||
|
if (plan == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int count = 0;
|
||||||
|
for (BusinessLoopStateMachine stateMachine : safe(plan.getStateMachines()))
|
||||||
|
{
|
||||||
|
if (stateMachine != null)
|
||||||
|
{
|
||||||
|
count += size(stateMachine.getTransitions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (BusinessLoopAction action : safe(plan.getActions()))
|
||||||
|
{
|
||||||
|
if (action != null)
|
||||||
|
{
|
||||||
|
count += size(action.getStateTransitions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalize(String value)
|
||||||
|
{
|
||||||
|
return StringUtils.defaultString(value).toLowerCase(Locale.ENGLISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> List<T> safe(List<T> items)
|
||||||
|
{
|
||||||
|
return items == null ? java.util.Collections.<T>emptyList() : items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int size(List<?> items)
|
||||||
|
{
|
||||||
|
return items == null ? 0 : items.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package com.ruoyi.generator.service.front;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopAction;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopObject;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopStateMachine;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class BusinessLoopPlanValidator
|
||||||
|
{
|
||||||
|
private static final int MAX_CORE_OBJECTS = 20;
|
||||||
|
private static final int MAX_ACTIONS = 30;
|
||||||
|
private static final Pattern ACTION_CODE = Pattern.compile("^[a-z][a-z0-9_]{1,63}$");
|
||||||
|
|
||||||
|
public void validate(BusinessLoopPlan plan)
|
||||||
|
{
|
||||||
|
if (plan == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("Business loop plan is required");
|
||||||
|
}
|
||||||
|
validateCoreObjects(plan.getCoreObjects());
|
||||||
|
validateActions(plan.getActions());
|
||||||
|
if (countStateTransitions(plan) == 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("Business loop must contain at least one state transition");
|
||||||
|
}
|
||||||
|
if (size(plan.getQuantityRules()) == 0 && size(plan.getRecordRules()) == 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("Business loop must contain at least one quantity or record rule");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateCoreObjects(List<BusinessLoopObject> coreObjects)
|
||||||
|
{
|
||||||
|
if (size(coreObjects) == 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("Business loop core objects cannot be empty");
|
||||||
|
}
|
||||||
|
if (coreObjects.size() > MAX_CORE_OBJECTS)
|
||||||
|
{
|
||||||
|
throw new ServiceException("Business loop core object count exceeds limit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateActions(List<BusinessLoopAction> actions)
|
||||||
|
{
|
||||||
|
if (size(actions) == 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("Business loop actions cannot be empty");
|
||||||
|
}
|
||||||
|
if (actions.size() > MAX_ACTIONS)
|
||||||
|
{
|
||||||
|
throw new ServiceException("Business loop action count exceeds limit");
|
||||||
|
}
|
||||||
|
Set<String> actionCodes = new HashSet<String>();
|
||||||
|
boolean hasRequiredAction = false;
|
||||||
|
for (BusinessLoopAction action : actions)
|
||||||
|
{
|
||||||
|
if (action == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("Business loop action cannot be empty");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(action.getCode()) || !ACTION_CODE.matcher(action.getCode()).matches())
|
||||||
|
{
|
||||||
|
throw new ServiceException("Business loop action code is invalid");
|
||||||
|
}
|
||||||
|
if (!actionCodes.add(action.getCode()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("Business loop action code duplicated: " + action.getCode());
|
||||||
|
}
|
||||||
|
hasRequiredAction = hasRequiredAction || action.isRequired();
|
||||||
|
}
|
||||||
|
if (!hasRequiredAction)
|
||||||
|
{
|
||||||
|
throw new ServiceException("Business loop must contain at least one required action");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countStateTransitions(BusinessLoopPlan plan)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
if (plan.getStateMachines() != null)
|
||||||
|
{
|
||||||
|
for (BusinessLoopStateMachine stateMachine : plan.getStateMachines())
|
||||||
|
{
|
||||||
|
if (stateMachine != null)
|
||||||
|
{
|
||||||
|
count += size(stateMachine.getTransitions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (plan.getActions() != null)
|
||||||
|
{
|
||||||
|
for (BusinessLoopAction action : plan.getActions())
|
||||||
|
{
|
||||||
|
if (action != null)
|
||||||
|
{
|
||||||
|
count += size(action.getStateTransitions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int size(List<?> items)
|
||||||
|
{
|
||||||
|
return items == null ? 0 : items.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
package com.ruoyi.generator.service.front;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.junit.Test;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopAuditResult;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopAction;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopObject;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopQuantityEffect;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopRecordEffect;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopStateMachine;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopStateTransition;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.DatabaseColumnDesign;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign;
|
||||||
|
|
||||||
|
public class BusinessLoopCoverageValidatorTest
|
||||||
|
{
|
||||||
|
private final BusinessLoopCoverageValidator validator = new BusinessLoopCoverageValidator();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void databaseAuditPassesWhenTablesAndFieldsCoverPlan()
|
||||||
|
{
|
||||||
|
BusinessLoopAuditResult result = validator.auditDatabase(completePlan(), completeDatabase());
|
||||||
|
|
||||||
|
assertTrue(result.isComplete());
|
||||||
|
assertTrue(result.getErrors().isEmpty());
|
||||||
|
assertEquals(1, result.getLoopActions());
|
||||||
|
assertEquals(2, result.getStateTransitions());
|
||||||
|
assertEquals(1, result.getQuantityRules());
|
||||||
|
assertEquals(1, result.getRecordRules());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void databaseAuditFailsWhenQuantityFieldMissing()
|
||||||
|
{
|
||||||
|
DatabaseDesignResponse database = completeDatabase();
|
||||||
|
database.getTables().get(1).setColumns(Collections.<DatabaseColumnDesign>emptyList());
|
||||||
|
|
||||||
|
BusinessLoopAuditResult result = validator.auditDatabase(completePlan(), database);
|
||||||
|
|
||||||
|
assertFalse(result.isComplete());
|
||||||
|
assertTrue(contains(result.getErrors().toString(), "available_count"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void businessActionAuditFailsWhenRequiredActionMissing()
|
||||||
|
{
|
||||||
|
BusinessLoopAuditResult result = validator.auditBusinessActions(completePlan(),
|
||||||
|
Collections.<BusinessActionDesign>emptyList());
|
||||||
|
|
||||||
|
assertFalse(result.isComplete());
|
||||||
|
assertTrue(result.getMissingRequiredActions().contains("confirm_borrow"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void businessActionAuditPassesWhenRequiredStatusAndQuantityEffectsExist()
|
||||||
|
{
|
||||||
|
BusinessActionDesign action = new BusinessActionDesign();
|
||||||
|
action.setCode("confirm_borrow");
|
||||||
|
action.setEffects(Arrays.asList(effect("SET_STATUS"), effect("DECREASE_NUMBER")));
|
||||||
|
|
||||||
|
BusinessLoopAuditResult result = validator.auditBusinessActions(completePlan(), Arrays.asList(action));
|
||||||
|
|
||||||
|
assertTrue(result.isComplete());
|
||||||
|
assertTrue(result.getMissingRequiredActions().isEmpty());
|
||||||
|
assertTrue(result.getErrors().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopPlan completePlan()
|
||||||
|
{
|
||||||
|
BusinessLoopPlan plan = new BusinessLoopPlan();
|
||||||
|
plan.setDomainName("library");
|
||||||
|
plan.setCoreObjects(Arrays.asList(coreObject()));
|
||||||
|
plan.setStateMachines(Arrays.asList(stateMachine()));
|
||||||
|
plan.setActions(Arrays.asList(requiredAction()));
|
||||||
|
plan.setQuantityRules(Arrays.asList(quantityRule()));
|
||||||
|
plan.setRecordRules(Arrays.asList(recordRule()));
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopObject coreObject()
|
||||||
|
{
|
||||||
|
BusinessLoopObject object = new BusinessLoopObject();
|
||||||
|
object.setCode("borrow_order");
|
||||||
|
object.setName("Borrow order");
|
||||||
|
object.setTableName("borrow_order");
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopStateMachine stateMachine()
|
||||||
|
{
|
||||||
|
BusinessLoopStateMachine stateMachine = new BusinessLoopStateMachine();
|
||||||
|
stateMachine.setObjectCode("borrow_order");
|
||||||
|
stateMachine.setTableName("borrow_order");
|
||||||
|
stateMachine.setStatusField("borrow_status");
|
||||||
|
stateMachine.setStates(Arrays.asList("submitted", "borrowed"));
|
||||||
|
stateMachine.setTransitions(Arrays.asList(transition()));
|
||||||
|
return stateMachine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopAction requiredAction()
|
||||||
|
{
|
||||||
|
BusinessLoopAction action = new BusinessLoopAction();
|
||||||
|
action.setCode("confirm_borrow");
|
||||||
|
action.setName("Confirm borrow");
|
||||||
|
action.setOwnerTable("borrow_order");
|
||||||
|
action.setRequired(true);
|
||||||
|
action.setStateTransitions(Arrays.asList(transition()));
|
||||||
|
action.setQuantityEffects(Arrays.asList(quantityRule()));
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopStateTransition transition()
|
||||||
|
{
|
||||||
|
BusinessLoopStateTransition transition = new BusinessLoopStateTransition();
|
||||||
|
transition.setActionCode("confirm_borrow");
|
||||||
|
transition.setFromState("submitted");
|
||||||
|
transition.setToState("borrowed");
|
||||||
|
return transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopQuantityEffect quantityRule()
|
||||||
|
{
|
||||||
|
BusinessLoopQuantityEffect effect = new BusinessLoopQuantityEffect();
|
||||||
|
effect.setActionCode("confirm_borrow");
|
||||||
|
effect.setObjectCode("book_stock");
|
||||||
|
effect.setTableName("book_stock");
|
||||||
|
effect.setQuantityField("available_count");
|
||||||
|
effect.setEffectType("decrease");
|
||||||
|
effect.setAmount("1");
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopRecordEffect recordRule()
|
||||||
|
{
|
||||||
|
BusinessLoopRecordEffect effect = new BusinessLoopRecordEffect();
|
||||||
|
effect.setActionCode("confirm_borrow");
|
||||||
|
effect.setObjectCode("borrow_record");
|
||||||
|
effect.setTableName("borrow_record");
|
||||||
|
effect.setOperation("insert");
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseDesignResponse completeDatabase()
|
||||||
|
{
|
||||||
|
DatabaseDesignResponse response = new DatabaseDesignResponse();
|
||||||
|
response.setTables(Arrays.asList(
|
||||||
|
table("borrow_order", "id", "borrow_status"),
|
||||||
|
table("book_stock", "id", "available_count"),
|
||||||
|
table("borrow_record", "id")));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseTableDesign table(String tableName, String... columnNames)
|
||||||
|
{
|
||||||
|
DatabaseTableDesign table = new DatabaseTableDesign();
|
||||||
|
table.setTableName(tableName);
|
||||||
|
DatabaseColumnDesign[] columns = new DatabaseColumnDesign[columnNames.length];
|
||||||
|
for (int i = 0; i < columnNames.length; i++)
|
||||||
|
{
|
||||||
|
columns[i] = column(columnNames[i]);
|
||||||
|
}
|
||||||
|
table.setColumns(Arrays.asList(columns));
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseColumnDesign column(String columnName)
|
||||||
|
{
|
||||||
|
DatabaseColumnDesign column = new DatabaseColumnDesign();
|
||||||
|
column.setColumnName(columnName);
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessActionEffectDesign effect(String type)
|
||||||
|
{
|
||||||
|
BusinessActionEffectDesign effect = new BusinessActionEffectDesign();
|
||||||
|
effect.setType(type);
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contains(String text, String needle)
|
||||||
|
{
|
||||||
|
return text != null && text.contains(needle);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package com.ruoyi.generator.service.front;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.junit.Test;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopAction;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopObject;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopQuantityEffect;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopRecordEffect;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopStateMachine;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.BusinessLoopPlan.BusinessLoopStateTransition;
|
||||||
|
|
||||||
|
public class BusinessLoopPlanValidatorTest
|
||||||
|
{
|
||||||
|
private final BusinessLoopPlanValidator validator = new BusinessLoopPlanValidator();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void acceptsCompleteLoopPlan()
|
||||||
|
{
|
||||||
|
validator.validate(completePlan());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectsPlanWithoutStateTransition()
|
||||||
|
{
|
||||||
|
BusinessLoopPlan plan = completePlan();
|
||||||
|
plan.getStateMachines().get(0).setTransitions(Collections.<BusinessLoopStateTransition>emptyList());
|
||||||
|
plan.getActions().get(0).setStateTransitions(Collections.<BusinessLoopStateTransition>emptyList());
|
||||||
|
|
||||||
|
ServiceException exception = expectServiceException(new ThrowingRunnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
validator.validate(plan);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("state transition"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectsPlanWithoutQuantityOrRecordRule()
|
||||||
|
{
|
||||||
|
BusinessLoopPlan plan = completePlan();
|
||||||
|
plan.setQuantityRules(Collections.<BusinessLoopQuantityEffect>emptyList());
|
||||||
|
plan.setRecordRules(Collections.<BusinessLoopRecordEffect>emptyList());
|
||||||
|
|
||||||
|
ServiceException exception = expectServiceException(new ThrowingRunnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
validator.validate(plan);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("quantity or record"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectsDuplicatedActionCode()
|
||||||
|
{
|
||||||
|
BusinessLoopPlan plan = completePlan();
|
||||||
|
plan.setActions(Arrays.asList(requiredAction("confirm_borrow"), requiredAction("confirm_borrow")));
|
||||||
|
|
||||||
|
ServiceException exception = expectServiceException(new ThrowingRunnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
validator.validate(plan);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("duplicated"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectsPlanWithoutRequiredAction()
|
||||||
|
{
|
||||||
|
BusinessLoopPlan plan = completePlan();
|
||||||
|
plan.getActions().get(0).setRequired(false);
|
||||||
|
|
||||||
|
ServiceException exception = expectServiceException(new ThrowingRunnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
validator.validate(plan);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("required action"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopPlan completePlan()
|
||||||
|
{
|
||||||
|
BusinessLoopPlan plan = new BusinessLoopPlan();
|
||||||
|
plan.setDomainName("library");
|
||||||
|
plan.setCoreObjects(Arrays.asList(coreObject()));
|
||||||
|
plan.setStateMachines(Arrays.asList(stateMachine()));
|
||||||
|
plan.setActions(Arrays.asList(requiredAction("confirm_borrow")));
|
||||||
|
plan.setQuantityRules(Arrays.asList(quantityRule()));
|
||||||
|
plan.setRecordRules(Arrays.asList(recordRule()));
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopObject coreObject()
|
||||||
|
{
|
||||||
|
BusinessLoopObject object = new BusinessLoopObject();
|
||||||
|
object.setCode("borrow_order");
|
||||||
|
object.setName("Borrow order");
|
||||||
|
object.setTableName("borrow_order");
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopStateMachine stateMachine()
|
||||||
|
{
|
||||||
|
BusinessLoopStateMachine stateMachine = new BusinessLoopStateMachine();
|
||||||
|
stateMachine.setObjectCode("borrow_order");
|
||||||
|
stateMachine.setTableName("borrow_order");
|
||||||
|
stateMachine.setStatusField("borrow_status");
|
||||||
|
stateMachine.setStates(Arrays.asList("submitted", "borrowed"));
|
||||||
|
stateMachine.setTransitions(Arrays.asList(transition()));
|
||||||
|
return stateMachine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopAction requiredAction(String code)
|
||||||
|
{
|
||||||
|
BusinessLoopAction action = new BusinessLoopAction();
|
||||||
|
action.setCode(code);
|
||||||
|
action.setName("Confirm borrow");
|
||||||
|
action.setActor("librarian");
|
||||||
|
action.setOwnerObject("borrow_order");
|
||||||
|
action.setOwnerTable("borrow_order");
|
||||||
|
action.setRequired(true);
|
||||||
|
action.setStateTransitions(Arrays.asList(transition()));
|
||||||
|
action.setQuantityEffects(Arrays.asList(quantityRule()));
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopStateTransition transition()
|
||||||
|
{
|
||||||
|
BusinessLoopStateTransition transition = new BusinessLoopStateTransition();
|
||||||
|
transition.setActionCode("confirm_borrow");
|
||||||
|
transition.setFromState("submitted");
|
||||||
|
transition.setToState("borrowed");
|
||||||
|
return transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopQuantityEffect quantityRule()
|
||||||
|
{
|
||||||
|
BusinessLoopQuantityEffect effect = new BusinessLoopQuantityEffect();
|
||||||
|
effect.setActionCode("confirm_borrow");
|
||||||
|
effect.setObjectCode("book_stock");
|
||||||
|
effect.setTableName("book_stock");
|
||||||
|
effect.setQuantityField("available_count");
|
||||||
|
effect.setEffectType("decrease");
|
||||||
|
effect.setAmount("1");
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessLoopRecordEffect recordRule()
|
||||||
|
{
|
||||||
|
BusinessLoopRecordEffect effect = new BusinessLoopRecordEffect();
|
||||||
|
effect.setActionCode("confirm_borrow");
|
||||||
|
effect.setObjectCode("borrow_record");
|
||||||
|
effect.setTableName("borrow_record");
|
||||||
|
effect.setOperation("insert");
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceException expectServiceException(ThrowingRunnable runnable)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
catch (ServiceException e)
|
||||||
|
{
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
throw new AssertionError("Expected ServiceException");
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ThrowingRunnable
|
||||||
|
{
|
||||||
|
void run();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user