feat: 工作流增加条件判断节点,重构部分UI
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
package tech.easyflow.ai.node;
|
||||
|
||||
import com.easyagents.flow.core.chain.*;
|
||||
import com.easyagents.flow.core.chain.repository.InMemoryChainStateRepository;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ConditionNodeTest {
|
||||
|
||||
@Test
|
||||
public void testFirstMatch() {
|
||||
ConditionNode node = new ConditionNode();
|
||||
|
||||
ConditionNode.ConditionBranch b1 = visualBranch("branch_a", "分支A",
|
||||
visualRule("ctx.score", "gte", "fixed", "80", null));
|
||||
ConditionNode.ConditionBranch b2 = visualBranch("branch_b", "分支B",
|
||||
visualRule("ctx.score", "gte", "fixed", "60", null));
|
||||
ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
|
||||
|
||||
node.setBranches(Arrays.asList(b1, b2, def));
|
||||
node.setDefaultBranchId(def.getId());
|
||||
node.setDefaultBranchLabel(def.getLabel());
|
||||
|
||||
Chain chain = createChain(Map.of("ctx", Map.of("score", 90)));
|
||||
Map<String, Object> result = node.execute(chain);
|
||||
|
||||
Assert.assertEquals("branch_a", result.get("matchedBranchId"));
|
||||
Assert.assertEquals("分支A", result.get("matchedBranchLabel"));
|
||||
Assert.assertEquals(false, result.get("matchedByDefault"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultBranchWhenNoMatch() {
|
||||
ConditionNode node = new ConditionNode();
|
||||
|
||||
ConditionNode.ConditionBranch b1 = visualBranch("branch_a", "分支A",
|
||||
visualRule("ctx.level", "eq", "fixed", "vip", null));
|
||||
ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
|
||||
|
||||
node.setBranches(Arrays.asList(b1, def));
|
||||
node.setDefaultBranchId(def.getId());
|
||||
node.setDefaultBranchLabel(def.getLabel());
|
||||
|
||||
Chain chain = createChain(Map.of("ctx", Map.of("level", "normal")));
|
||||
Map<String, Object> result = node.execute(chain);
|
||||
|
||||
Assert.assertEquals("branch_default", result.get("matchedBranchId"));
|
||||
Assert.assertEquals("默认分支", result.get("matchedBranchLabel"));
|
||||
Assert.assertEquals(true, result.get("matchedByDefault"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVisualOperators() {
|
||||
ConditionNode node = new ConditionNode();
|
||||
|
||||
ConditionNode.ConditionBranch b1 = visualBranch("branch_hit", "命中分支",
|
||||
visualRule("ctx.amount", "gt", "fixed", "100", null),
|
||||
visualRule("ctx.tags", "contains", "fixed", "vip", null),
|
||||
visualRule("ctx.title", "notContains", "fixed", "blacklist", null),
|
||||
visualRule("ctx.emptyText", "isEmpty", "fixed", "", null),
|
||||
visualRule("ctx.note", "isNotEmpty", "fixed", "", null));
|
||||
ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
|
||||
|
||||
node.setBranches(Arrays.asList(b1, def));
|
||||
node.setDefaultBranchId(def.getId());
|
||||
node.setDefaultBranchLabel(def.getLabel());
|
||||
|
||||
Map<String, Object> ctx = new HashMap<>();
|
||||
ctx.put("amount", 120);
|
||||
ctx.put("tags", Arrays.asList("vip", "new"));
|
||||
ctx.put("title", "white-user");
|
||||
ctx.put("emptyText", "");
|
||||
ctx.put("note", "ok");
|
||||
|
||||
Chain chain = createChain(Map.of("ctx", ctx));
|
||||
Map<String, Object> result = node.execute(chain);
|
||||
Assert.assertEquals("branch_hit", result.get("matchedBranchId"));
|
||||
Assert.assertEquals(false, result.get("matchedByDefault"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVisualRuleJoinerOr() {
|
||||
ConditionNode node = new ConditionNode();
|
||||
|
||||
ConditionNode.ConditionBranch b1 = visualBranch("branch_or", "OR 分支",
|
||||
visualRule("ctx.score", "gt", "fixed", "100", null, "AND"),
|
||||
visualRule("ctx.level", "eq", "fixed", "vip", null, "OR"));
|
||||
ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
|
||||
|
||||
node.setBranches(Arrays.asList(b1, def));
|
||||
node.setDefaultBranchId(def.getId());
|
||||
node.setDefaultBranchLabel(def.getLabel());
|
||||
|
||||
Chain chain = createChain(Map.of("ctx", Map.of("score", 80, "level", "vip")));
|
||||
Map<String, Object> result = node.execute(chain);
|
||||
|
||||
Assert.assertEquals("branch_or", result.get("matchedBranchId"));
|
||||
Assert.assertEquals(false, result.get("matchedByDefault"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpressionModeAndInvalidExpression() {
|
||||
ConditionNode node = new ConditionNode();
|
||||
|
||||
ConditionNode.ConditionBranch b1 = expressionBranch("branch_expr", "表达式分支",
|
||||
"{{amount}} >= 100 && {{level}} === 'vip'");
|
||||
ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
|
||||
node.setBranches(Arrays.asList(b1, def));
|
||||
node.setDefaultBranchId(def.getId());
|
||||
node.setDefaultBranchLabel(def.getLabel());
|
||||
|
||||
Chain chain = createChain(Map.of("amount", 120, "level", "vip"));
|
||||
Map<String, Object> result = node.execute(chain);
|
||||
Assert.assertEquals("branch_expr", result.get("matchedBranchId"));
|
||||
|
||||
ConditionNode invalidNode = new ConditionNode();
|
||||
ConditionNode.ConditionBranch bad = expressionBranch("branch_bad", "异常分支", "amount >");
|
||||
invalidNode.setBranches(Arrays.asList(bad, def));
|
||||
invalidNode.setDefaultBranchId(def.getId());
|
||||
invalidNode.setDefaultBranchLabel(def.getLabel());
|
||||
|
||||
ChainException ex = Assert.assertThrows(ChainException.class, () -> invalidNode.execute(chain));
|
||||
Assert.assertTrue(ex.getMessage().contains("branch_bad"));
|
||||
Assert.assertTrue(ex.getMessage().contains("异常分支"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpressionModeWithTemplatePath() {
|
||||
ConditionNode node = new ConditionNode();
|
||||
|
||||
ConditionNode.ConditionBranch b1 = expressionBranch(
|
||||
"branch_expr_tpl",
|
||||
"模板分支",
|
||||
"{{ctx.order.amount}} >= 10 && {{ctx.user.level}} === 'vip'"
|
||||
);
|
||||
ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
|
||||
node.setBranches(Arrays.asList(b1, def));
|
||||
node.setDefaultBranchId(def.getId());
|
||||
node.setDefaultBranchLabel(def.getLabel());
|
||||
|
||||
Map<String, Object> ctx = new HashMap<>();
|
||||
ctx.put("order", Map.of("amount", 18));
|
||||
ctx.put("user", Map.of("level", "vip"));
|
||||
Chain chain = createChain(Map.of("ctx", ctx));
|
||||
|
||||
Map<String, Object> result = node.execute(chain);
|
||||
Assert.assertEquals("branch_expr_tpl", result.get("matchedBranchId"));
|
||||
Assert.assertEquals(false, result.get("matchedByDefault"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManagedEdgeConditionRouting() {
|
||||
ConditionNode node = new ConditionNode();
|
||||
|
||||
ConditionNode.ConditionBranch b1 = visualBranch("branch_yes", "通过",
|
||||
visualRule("ctx.allow", "eq", "fixed", "true", null));
|
||||
ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
|
||||
node.setBranches(Arrays.asList(b1, def));
|
||||
node.setDefaultBranchId(def.getId());
|
||||
node.setDefaultBranchLabel(def.getLabel());
|
||||
|
||||
Chain chain = createChain(Map.of("ctx", Map.of("allow", true)));
|
||||
Map<String, Object> result = node.execute(chain);
|
||||
|
||||
Edge yesEdge = new Edge("e_yes");
|
||||
yesEdge.setCondition(new JsCodeCondition("matchedBranchId === 'branch_yes'"));
|
||||
Assert.assertTrue(yesEdge.getCondition().check(chain, yesEdge, result));
|
||||
|
||||
Edge noEdge = new Edge("e_no");
|
||||
noEdge.setCondition(new JsCodeCondition("matchedBranchId === 'branch_other'"));
|
||||
Assert.assertFalse(noEdge.getCondition().check(chain, noEdge, result));
|
||||
}
|
||||
|
||||
private static Chain createChain(Map<String, Object> memory) {
|
||||
Chain chain = new Chain(new ChainDefinition(), UUID.randomUUID().toString());
|
||||
chain.setChainStateRepository(new InMemoryChainStateRepository());
|
||||
if (memory != null && !memory.isEmpty()) {
|
||||
chain.getState().getMemory().putAll(memory);
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
private static ConditionNode.ConditionBranch defaultBranch(String id, String label) {
|
||||
ConditionNode.ConditionBranch branch = new ConditionNode.ConditionBranch();
|
||||
branch.setId(id);
|
||||
branch.setLabel(label);
|
||||
branch.setMode("visual");
|
||||
branch.setRules(Collections.emptyList());
|
||||
return branch;
|
||||
}
|
||||
|
||||
private static ConditionNode.ConditionBranch expressionBranch(String id, String label, String expression) {
|
||||
ConditionNode.ConditionBranch branch = new ConditionNode.ConditionBranch();
|
||||
branch.setId(id);
|
||||
branch.setLabel(label);
|
||||
branch.setMode("expression");
|
||||
branch.setExpression(expression);
|
||||
branch.setRules(Collections.emptyList());
|
||||
return branch;
|
||||
}
|
||||
|
||||
private static ConditionNode.ConditionBranch visualBranch(
|
||||
String id, String label, ConditionNode.ConditionRule... rules) {
|
||||
ConditionNode.ConditionBranch branch = new ConditionNode.ConditionBranch();
|
||||
branch.setId(id);
|
||||
branch.setLabel(label);
|
||||
branch.setMode("visual");
|
||||
branch.setRules(Arrays.asList(rules));
|
||||
return branch;
|
||||
}
|
||||
|
||||
private static ConditionNode.ConditionRule visualRule(
|
||||
String leftRef, String operator, String rightType, String rightValue, String rightRef) {
|
||||
return visualRule(leftRef, operator, rightType, rightValue, rightRef, "AND");
|
||||
}
|
||||
|
||||
private static ConditionNode.ConditionRule visualRule(
|
||||
String leftRef, String operator, String rightType, String rightValue, String rightRef, String joiner) {
|
||||
ConditionNode.ConditionRule rule = new ConditionNode.ConditionRule();
|
||||
rule.setId(UUID.randomUUID().toString());
|
||||
rule.setJoiner(joiner);
|
||||
rule.setLeftRef(leftRef);
|
||||
rule.setOperator(operator);
|
||||
rule.setRightType(rightType);
|
||||
rule.setRightValue(rightValue);
|
||||
rule.setRightRef(rightRef);
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user