Fix business loop coverage audit matching
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.ruoyi.generator.service.front;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -83,6 +84,9 @@ public class BusinessLoopCoverageValidator
|
||||
finish(result);
|
||||
return result;
|
||||
}
|
||||
Map<String, List<ExpectedTarget>> stateTargetsByAction = collectStateTargetsByAction(plan);
|
||||
Map<String, List<ExpectedTarget>> quantityTargetsByAction = collectQuantityTargetsByAction(plan);
|
||||
Map<String, List<ExpectedTarget>> recordTargetsByAction = collectRecordTargetsByAction(plan);
|
||||
for (BusinessLoopAction planAction : safe(plan.getActions()))
|
||||
{
|
||||
if (planAction == null || !planAction.isRequired())
|
||||
@@ -95,40 +99,64 @@ public class BusinessLoopCoverageValidator
|
||||
result.getMissingRequiredActions().add(planAction.getCode());
|
||||
continue;
|
||||
}
|
||||
validateRequiredEffects(result, planAction, action);
|
||||
validateRequiredEffects(result, planAction, action, stateTargetsByAction, quantityTargetsByAction,
|
||||
recordTargetsByAction);
|
||||
}
|
||||
finish(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void validateRequiredEffects(BusinessLoopAuditResult result, BusinessLoopAction planAction,
|
||||
BusinessActionDesign action)
|
||||
BusinessActionDesign action, Map<String, List<ExpectedTarget>> stateTargetsByAction,
|
||||
Map<String, List<ExpectedTarget>> quantityTargetsByAction,
|
||||
Map<String, List<ExpectedTarget>> recordTargetsByAction)
|
||||
{
|
||||
if (size(planAction.getStateTransitions()) > 0 && !hasAnyEffect(action, "SET_STATUS", "UPDATE_FIELDS", "UPDATE_FIELD"))
|
||||
List<ExpectedTarget> stateTargets = safe(stateTargetsByAction.get(planAction.getCode()));
|
||||
List<ExpectedTarget> quantityTargets = safe(quantityTargetsByAction.get(planAction.getCode()));
|
||||
List<ExpectedTarget> recordTargets = safe(recordTargetsByAction.get(planAction.getCode()));
|
||||
if ((size(planAction.getStateTransitions()) > 0 || !stateTargets.isEmpty())
|
||||
&& !hasMatchingEffect(action, stateTargets, "SET_STATUS", "UPDATE_FIELDS", "UPDATE_FIELD"))
|
||||
{
|
||||
result.getErrors().add("Missing status effect for required action: " + planAction.getCode());
|
||||
result.getErrors().add("Missing status effect for required action " + planAction.getCode()
|
||||
+ formatTargets(stateTargets));
|
||||
}
|
||||
if (size(planAction.getQuantityEffects()) > 0 && !hasAnyEffect(action, "INCREASE_NUMBER", "DECREASE_NUMBER"))
|
||||
if (!quantityTargets.isEmpty()
|
||||
&& !hasMatchingEffect(action, quantityTargets, "INCREASE_NUMBER", "DECREASE_NUMBER"))
|
||||
{
|
||||
result.getErrors().add("Missing quantity effect for required action: " + planAction.getCode());
|
||||
result.getErrors().add("Missing quantity effect for required action " + planAction.getCode()
|
||||
+ formatTargets(quantityTargets));
|
||||
}
|
||||
if (size(planAction.getRecordEffects()) > 0 && !hasAnyEffect(action, "INSERT_ROW", "UPDATE_FIELDS"))
|
||||
if (!recordTargets.isEmpty() && !hasMatchingEffect(action, recordTargets, "INSERT_ROW", "UPDATE_FIELDS"))
|
||||
{
|
||||
result.getErrors().add("Missing record effect for required action: " + planAction.getCode());
|
||||
result.getErrors().add("Missing record effect for required action " + planAction.getCode()
|
||||
+ formatTargets(recordTargets));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasAnyEffect(BusinessActionDesign action, String... types)
|
||||
private boolean hasMatchingEffect(BusinessActionDesign action, List<ExpectedTarget> expectedTargets, String... types)
|
||||
{
|
||||
if (action == null || action.getEffects() == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Set<String> expectedTypes = new HashSet<String>();
|
||||
for (String type : types)
|
||||
if (expectedTargets == null || expectedTargets.isEmpty())
|
||||
{
|
||||
expectedTypes.add(type);
|
||||
return hasAnyEffectType(action, types);
|
||||
}
|
||||
Set<String> expectedTypes = collectExpectedTypes(types);
|
||||
for (ExpectedTarget expectedTarget : expectedTargets)
|
||||
{
|
||||
if (!hasMatchingEffect(action, expectedTarget, expectedTypes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasAnyEffectType(BusinessActionDesign action, String... types)
|
||||
{
|
||||
Set<String> expectedTypes = collectExpectedTypes(types);
|
||||
for (BusinessActionEffectDesign effect : action.getEffects())
|
||||
{
|
||||
if (effect == null)
|
||||
@@ -144,6 +172,56 @@ public class BusinessLoopCoverageValidator
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasMatchingEffect(BusinessActionDesign action, ExpectedTarget expectedTarget,
|
||||
Set<String> expectedTypes)
|
||||
{
|
||||
for (BusinessActionEffectDesign effect : action.getEffects())
|
||||
{
|
||||
if (effect == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String type = StringUtils.defaultString(effect.getType()).toUpperCase(Locale.ENGLISH);
|
||||
if (expectedTypes.contains(type) && matchesTarget(effect, expectedTarget))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean matchesTarget(BusinessActionEffectDesign effect, ExpectedTarget expectedTarget)
|
||||
{
|
||||
if (expectedTarget == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (StringUtils.isNotBlank(expectedTarget.getTableName())
|
||||
&& !normalize(expectedTarget.getTableName()).equals(normalize(effect.getTargetTable())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (StringUtils.isBlank(expectedTarget.getFieldName()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (normalize(expectedTarget.getFieldName()).equals(normalize(effect.getTargetField())))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return effect.getValues() != null && effect.getValues().containsKey(expectedTarget.getFieldName());
|
||||
}
|
||||
|
||||
private Set<String> collectExpectedTypes(String... types)
|
||||
{
|
||||
Set<String> expectedTypes = new HashSet<String>();
|
||||
for (String type : types)
|
||||
{
|
||||
expectedTypes.add(type);
|
||||
}
|
||||
return expectedTypes;
|
||||
}
|
||||
|
||||
private Map<String, BusinessActionDesign> collectActionsByCode(List<BusinessActionDesign> actions)
|
||||
{
|
||||
Map<String, BusinessActionDesign> actionsByCode = new HashMap<String, BusinessActionDesign>();
|
||||
@@ -161,6 +239,114 @@ public class BusinessLoopCoverageValidator
|
||||
return actionsByCode;
|
||||
}
|
||||
|
||||
private Map<String, List<ExpectedTarget>> collectStateTargetsByAction(BusinessLoopPlan plan)
|
||||
{
|
||||
Map<String, List<ExpectedTarget>> targetsByAction = new HashMap<String, List<ExpectedTarget>>();
|
||||
for (BusinessLoopStateMachine stateMachine : safe(plan.getStateMachines()))
|
||||
{
|
||||
if (stateMachine == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (BusinessLoopPlan.BusinessLoopStateTransition transition : safe(stateMachine.getTransitions()))
|
||||
{
|
||||
if (transition != null && StringUtils.isNotBlank(transition.getActionCode()))
|
||||
{
|
||||
addTarget(targetsByAction, transition.getActionCode(),
|
||||
new ExpectedTarget(stateMachine.getTableName(), stateMachine.getStatusField()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return targetsByAction;
|
||||
}
|
||||
|
||||
private Map<String, List<ExpectedTarget>> collectQuantityTargetsByAction(BusinessLoopPlan plan)
|
||||
{
|
||||
Map<String, List<ExpectedTarget>> targetsByAction = new HashMap<String, List<ExpectedTarget>>();
|
||||
for (BusinessLoopQuantityEffect quantityRule : safe(plan.getQuantityRules()))
|
||||
{
|
||||
addQuantityTarget(targetsByAction, quantityRule, null);
|
||||
}
|
||||
for (BusinessLoopAction action : safe(plan.getActions()))
|
||||
{
|
||||
if (action == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (BusinessLoopQuantityEffect quantityEffect : safe(action.getQuantityEffects()))
|
||||
{
|
||||
addQuantityTarget(targetsByAction, quantityEffect, action.getCode());
|
||||
}
|
||||
}
|
||||
return targetsByAction;
|
||||
}
|
||||
|
||||
private void addQuantityTarget(Map<String, List<ExpectedTarget>> targetsByAction,
|
||||
BusinessLoopQuantityEffect quantityEffect, String fallbackActionCode)
|
||||
{
|
||||
if (quantityEffect == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
String actionCode = StringUtils.isNotBlank(quantityEffect.getActionCode())
|
||||
? quantityEffect.getActionCode() : fallbackActionCode;
|
||||
if (StringUtils.isBlank(actionCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
addTarget(targetsByAction, actionCode,
|
||||
new ExpectedTarget(quantityEffect.getTableName(), quantityEffect.getQuantityField()));
|
||||
}
|
||||
|
||||
private Map<String, List<ExpectedTarget>> collectRecordTargetsByAction(BusinessLoopPlan plan)
|
||||
{
|
||||
Map<String, List<ExpectedTarget>> targetsByAction = new HashMap<String, List<ExpectedTarget>>();
|
||||
for (BusinessLoopRecordEffect recordRule : safe(plan.getRecordRules()))
|
||||
{
|
||||
addRecordTarget(targetsByAction, recordRule, null);
|
||||
}
|
||||
for (BusinessLoopAction action : safe(plan.getActions()))
|
||||
{
|
||||
if (action == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (BusinessLoopRecordEffect recordEffect : safe(action.getRecordEffects()))
|
||||
{
|
||||
addRecordTarget(targetsByAction, recordEffect, action.getCode());
|
||||
}
|
||||
}
|
||||
return targetsByAction;
|
||||
}
|
||||
|
||||
private void addRecordTarget(Map<String, List<ExpectedTarget>> targetsByAction,
|
||||
BusinessLoopRecordEffect recordEffect, String fallbackActionCode)
|
||||
{
|
||||
if (recordEffect == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
String actionCode = StringUtils.isNotBlank(recordEffect.getActionCode())
|
||||
? recordEffect.getActionCode() : fallbackActionCode;
|
||||
if (StringUtils.isBlank(actionCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
addTarget(targetsByAction, actionCode, new ExpectedTarget(recordEffect.getTableName(), null));
|
||||
}
|
||||
|
||||
private void addTarget(Map<String, List<ExpectedTarget>> targetsByAction, String actionCode,
|
||||
ExpectedTarget target)
|
||||
{
|
||||
List<ExpectedTarget> targets = targetsByAction.get(actionCode);
|
||||
if (targets == null)
|
||||
{
|
||||
targets = new ArrayList<ExpectedTarget>();
|
||||
targetsByAction.put(actionCode, targets);
|
||||
}
|
||||
targets.add(target);
|
||||
}
|
||||
|
||||
private Map<String, Set<String>> collectColumnsByTable(DatabaseDesignResponse database)
|
||||
{
|
||||
Map<String, Set<String>> columnsByTable = new HashMap<String, Set<String>>();
|
||||
@@ -245,6 +431,24 @@ public class BusinessLoopCoverageValidator
|
||||
return StringUtils.defaultString(value).toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
private String formatTargets(List<ExpectedTarget> targets)
|
||||
{
|
||||
if (targets == null || targets.isEmpty())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
StringBuilder builder = new StringBuilder(": ");
|
||||
for (int i = 0; i < targets.size(); i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append(targets.get(i).format());
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private <T> List<T> safe(List<T> items)
|
||||
{
|
||||
return items == null ? java.util.Collections.<T>emptyList() : items;
|
||||
@@ -254,4 +458,35 @@ public class BusinessLoopCoverageValidator
|
||||
{
|
||||
return items == null ? 0 : items.size();
|
||||
}
|
||||
|
||||
private static class ExpectedTarget
|
||||
{
|
||||
private final String tableName;
|
||||
private final String fieldName;
|
||||
|
||||
ExpectedTarget(String tableName, String fieldName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
String getTableName()
|
||||
{
|
||||
return tableName;
|
||||
}
|
||||
|
||||
String getFieldName()
|
||||
{
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
String format()
|
||||
{
|
||||
if (StringUtils.isBlank(fieldName))
|
||||
{
|
||||
return StringUtils.defaultString(tableName);
|
||||
}
|
||||
return StringUtils.defaultString(tableName) + "." + fieldName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,10 @@ public class BusinessLoopCoverageValidatorTest
|
||||
{
|
||||
BusinessActionDesign action = new BusinessActionDesign();
|
||||
action.setCode("confirm_borrow");
|
||||
action.setEffects(Arrays.asList(effect("SET_STATUS"), effect("DECREASE_NUMBER")));
|
||||
action.setEffects(Arrays.asList(
|
||||
effect("SET_STATUS", "borrow_order", "borrow_status"),
|
||||
effect("DECREASE_NUMBER", "book_stock", "available_count"),
|
||||
effect("INSERT_ROW", "borrow_record", null)));
|
||||
|
||||
BusinessLoopAuditResult result = validator.auditBusinessActions(completePlan(), Arrays.asList(action));
|
||||
|
||||
@@ -74,6 +77,60 @@ public class BusinessLoopCoverageValidatorTest
|
||||
assertTrue(result.getErrors().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void businessActionAuditFailsWhenTopLevelRecordRuleMissingEffect()
|
||||
{
|
||||
BusinessActionDesign action = new BusinessActionDesign();
|
||||
action.setCode("confirm_borrow");
|
||||
action.setEffects(Arrays.asList(
|
||||
effect("SET_STATUS", "borrow_order", "borrow_status"),
|
||||
effect("DECREASE_NUMBER", "book_stock", "available_count")));
|
||||
|
||||
BusinessLoopAuditResult result = validator.auditBusinessActions(completePlan(), Arrays.asList(action));
|
||||
|
||||
assertFalse(result.isComplete());
|
||||
assertTrue(contains(result.getErrors().toString(), "record effect")
|
||||
|| contains(result.getErrors().toString(), "confirm_borrow"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void businessActionAuditFailsWhenTopLevelQuantityRuleMatchesWrongField()
|
||||
{
|
||||
BusinessLoopPlan plan = completePlan();
|
||||
plan.getActions().get(0).setQuantityEffects(Collections.<BusinessLoopQuantityEffect>emptyList());
|
||||
BusinessActionDesign action = new BusinessActionDesign();
|
||||
action.setCode("confirm_borrow");
|
||||
action.setEffects(Arrays.asList(
|
||||
effect("SET_STATUS", "borrow_order", "borrow_status"),
|
||||
effect("DECREASE_NUMBER", "book_stock", "borrowed_count"),
|
||||
effect("INSERT_ROW", "borrow_record", null)));
|
||||
|
||||
BusinessLoopAuditResult result = validator.auditBusinessActions(plan, Arrays.asList(action));
|
||||
|
||||
assertFalse(result.isComplete());
|
||||
assertTrue(contains(result.getErrors().toString(), "available_count")
|
||||
|| contains(result.getErrors().toString(), "quantity effect"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void businessActionAuditFailsWhenStateStatusEffectMatchesWrongField()
|
||||
{
|
||||
BusinessLoopPlan plan = completePlan();
|
||||
plan.getActions().get(0).setStateTransitions(Collections.<BusinessLoopStateTransition>emptyList());
|
||||
BusinessActionDesign action = new BusinessActionDesign();
|
||||
action.setCode("confirm_borrow");
|
||||
action.setEffects(Arrays.asList(
|
||||
effect("SET_STATUS", "borrow_order", "state"),
|
||||
effect("DECREASE_NUMBER", "book_stock", "available_count"),
|
||||
effect("INSERT_ROW", "borrow_record", null)));
|
||||
|
||||
BusinessLoopAuditResult result = validator.auditBusinessActions(plan, Arrays.asList(action));
|
||||
|
||||
assertFalse(result.isComplete());
|
||||
assertTrue(contains(result.getErrors().toString(), "borrow_status")
|
||||
|| contains(result.getErrors().toString(), "status effect"));
|
||||
}
|
||||
|
||||
private BusinessLoopPlan completePlan()
|
||||
{
|
||||
BusinessLoopPlan plan = new BusinessLoopPlan();
|
||||
@@ -180,9 +237,16 @@ public class BusinessLoopCoverageValidatorTest
|
||||
}
|
||||
|
||||
private BusinessActionEffectDesign effect(String type)
|
||||
{
|
||||
return effect(type, null, null);
|
||||
}
|
||||
|
||||
private BusinessActionEffectDesign effect(String type, String targetTable, String targetField)
|
||||
{
|
||||
BusinessActionEffectDesign effect = new BusinessActionEffectDesign();
|
||||
effect.setType(type);
|
||||
effect.setTargetTable(targetTable);
|
||||
effect.setTargetField(targetField);
|
||||
return effect;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user