Fix business loop coverage audit matching
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.ruoyi.generator.service.front;
|
package com.ruoyi.generator.service.front;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -83,6 +84,9 @@ public class BusinessLoopCoverageValidator
|
|||||||
finish(result);
|
finish(result);
|
||||||
return 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()))
|
for (BusinessLoopAction planAction : safe(plan.getActions()))
|
||||||
{
|
{
|
||||||
if (planAction == null || !planAction.isRequired())
|
if (planAction == null || !planAction.isRequired())
|
||||||
@@ -95,40 +99,64 @@ public class BusinessLoopCoverageValidator
|
|||||||
result.getMissingRequiredActions().add(planAction.getCode());
|
result.getMissingRequiredActions().add(planAction.getCode());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
validateRequiredEffects(result, planAction, action);
|
validateRequiredEffects(result, planAction, action, stateTargetsByAction, quantityTargetsByAction,
|
||||||
|
recordTargetsByAction);
|
||||||
}
|
}
|
||||||
finish(result);
|
finish(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateRequiredEffects(BusinessLoopAuditResult result, BusinessLoopAction planAction,
|
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)
|
if (action == null || action.getEffects() == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Set<String> expectedTypes = new HashSet<String>();
|
if (expectedTargets == null || expectedTargets.isEmpty())
|
||||||
for (String type : types)
|
|
||||||
{
|
{
|
||||||
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())
|
for (BusinessActionEffectDesign effect : action.getEffects())
|
||||||
{
|
{
|
||||||
if (effect == null)
|
if (effect == null)
|
||||||
@@ -144,6 +172,56 @@ public class BusinessLoopCoverageValidator
|
|||||||
return false;
|
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)
|
private Map<String, BusinessActionDesign> collectActionsByCode(List<BusinessActionDesign> actions)
|
||||||
{
|
{
|
||||||
Map<String, BusinessActionDesign> actionsByCode = new HashMap<String, BusinessActionDesign>();
|
Map<String, BusinessActionDesign> actionsByCode = new HashMap<String, BusinessActionDesign>();
|
||||||
@@ -161,6 +239,114 @@ public class BusinessLoopCoverageValidator
|
|||||||
return actionsByCode;
|
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)
|
private Map<String, Set<String>> collectColumnsByTable(DatabaseDesignResponse database)
|
||||||
{
|
{
|
||||||
Map<String, Set<String>> columnsByTable = new HashMap<String, Set<String>>();
|
Map<String, Set<String>> columnsByTable = new HashMap<String, Set<String>>();
|
||||||
@@ -245,6 +431,24 @@ public class BusinessLoopCoverageValidator
|
|||||||
return StringUtils.defaultString(value).toLowerCase(Locale.ENGLISH);
|
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)
|
private <T> List<T> safe(List<T> items)
|
||||||
{
|
{
|
||||||
return items == null ? java.util.Collections.<T>emptyList() : items;
|
return items == null ? java.util.Collections.<T>emptyList() : items;
|
||||||
@@ -254,4 +458,35 @@ public class BusinessLoopCoverageValidator
|
|||||||
{
|
{
|
||||||
return items == null ? 0 : items.size();
|
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();
|
BusinessActionDesign action = new BusinessActionDesign();
|
||||||
action.setCode("confirm_borrow");
|
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));
|
BusinessLoopAuditResult result = validator.auditBusinessActions(completePlan(), Arrays.asList(action));
|
||||||
|
|
||||||
@@ -74,6 +77,60 @@ public class BusinessLoopCoverageValidatorTest
|
|||||||
assertTrue(result.getErrors().isEmpty());
|
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()
|
private BusinessLoopPlan completePlan()
|
||||||
{
|
{
|
||||||
BusinessLoopPlan plan = new BusinessLoopPlan();
|
BusinessLoopPlan plan = new BusinessLoopPlan();
|
||||||
@@ -180,9 +237,16 @@ public class BusinessLoopCoverageValidatorTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
private BusinessActionEffectDesign effect(String type)
|
private BusinessActionEffectDesign effect(String type)
|
||||||
|
{
|
||||||
|
return effect(type, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessActionEffectDesign effect(String type, String targetTable, String targetField)
|
||||||
{
|
{
|
||||||
BusinessActionEffectDesign effect = new BusinessActionEffectDesign();
|
BusinessActionEffectDesign effect = new BusinessActionEffectDesign();
|
||||||
effect.setType(type);
|
effect.setType(type);
|
||||||
|
effect.setTargetTable(targetTable);
|
||||||
|
effect.setTargetField(targetField);
|
||||||
return effect;
|
return effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user