feat: 工作流增加条件判断节点,重构部分UI

This commit is contained in:
2026-03-04 17:35:49 +08:00
parent 67d42a80b9
commit 27376a5f33
14 changed files with 2481 additions and 31 deletions

View File

@@ -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;
}
}