feat: 完善循环节点配置与作用域输出
- 支持次数与数组独立或组合配置并补齐检查规则 - 统一循环体临时变量与下游正式输出候选 - 稳定知识库对象数组字段并补充前后端测试
This commit is contained in:
@@ -17,6 +17,9 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 为工作流知识库节点提供检索能力。
|
||||
*/
|
||||
@Component
|
||||
public class KnowledgeProviderImpl implements KnowledgeProvider {
|
||||
|
||||
@@ -24,14 +27,23 @@ public class KnowledgeProviderImpl implements KnowledgeProvider {
|
||||
private DocumentCollectionService documentCollectionService;
|
||||
|
||||
/**
|
||||
* 获取知识库
|
||||
* @param id 知识库id
|
||||
* 获取知识库检索器。
|
||||
*
|
||||
* @param id 知识库 ID
|
||||
* @return 知识库检索器
|
||||
*/
|
||||
@Override
|
||||
public Knowledge getKnowledge(Object id) {
|
||||
return new Knowledge() {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> search(String keyword, int limit, KnowledgeNode knowledgeNode, Chain chain) {
|
||||
public List<Map<String, Object>> search(
|
||||
String keyword,
|
||||
int limit,
|
||||
KnowledgeNode knowledgeNode,
|
||||
Chain chain) {
|
||||
KnowledgeRetrievalRequest request = new KnowledgeRetrievalRequest();
|
||||
request.setKnowledgeId(new BigInteger(id.toString()));
|
||||
request.setQuery(keyword);
|
||||
@@ -45,10 +57,29 @@ public class KnowledgeProviderImpl implements KnowledgeProvider {
|
||||
}
|
||||
List<Map<String, Object>> res = new ArrayList<>();
|
||||
for (Document document : documents) {
|
||||
res.add(JSONObject.from(document));
|
||||
res.add(toWorkflowDocument(document, id));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将检索文档转换为工作流稳定对象,并保留旧序列化字段。
|
||||
*
|
||||
* @param document 检索文档
|
||||
* @param knowledgeId 知识库 ID
|
||||
* @return 工作流文档对象
|
||||
*/
|
||||
private Map<String, Object> toWorkflowDocument(
|
||||
Document document, Object knowledgeId) {
|
||||
JSONObject result = JSONObject.from(document);
|
||||
result.put("title", document.getTitle());
|
||||
result.put("content", document.getContent());
|
||||
result.put(
|
||||
"documentId",
|
||||
document.getMetadata("documentId", document.getId()));
|
||||
result.put("knowledgeId", knowledgeId);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 校验工作流结构、节点配置和预执行约束。
|
||||
*/
|
||||
@Service
|
||||
public class WorkflowCheckService {
|
||||
private static final String LEVEL_ERROR = "ERROR";
|
||||
@@ -240,7 +243,7 @@ public class WorkflowCheckService {
|
||||
Set<String> issueKeys) {
|
||||
for (NodeView node : nodes) {
|
||||
checkConfiguredLoopCount(node, issues, issueKeys);
|
||||
checkFixedExplicitLoopCount(node, issues, issueKeys);
|
||||
checkExplicitLoopInputs(node, issues, issueKeys);
|
||||
if (StringUtils.hasText(node.parentId)) {
|
||||
NodeView parent = nodeMap.get(node.parentId);
|
||||
if (parent != null && !TYPE_LOOP.equals(parent.type)) {
|
||||
@@ -255,6 +258,7 @@ public class WorkflowCheckService {
|
||||
}
|
||||
}
|
||||
checkLoopParentCycle(node, nodeMap, issues, issueKeys);
|
||||
checkLoopVariableScope(node, nodeMap, issues, issueKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,36 +285,293 @@ public class WorkflowCheckService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验显式循环节点使用固定数值时的次数范围。
|
||||
* 校验显式循环节点的新旧输入结构。
|
||||
*
|
||||
* @param node 节点
|
||||
* @param issues 问题列表
|
||||
* @param issueKeys 问题去重键
|
||||
*/
|
||||
private void checkFixedExplicitLoopCount(
|
||||
private void checkExplicitLoopInputs(
|
||||
NodeView node,
|
||||
List<WorkflowCheckIssue> issues,
|
||||
Set<String> issueKeys) {
|
||||
if (!TYPE_LOOP.equals(node.type) || node.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject loopInputs = node.data.getJSONObject("loopInputs");
|
||||
JSONArray loopVars = node.data.getJSONArray("loopVars");
|
||||
if (loopVars == null || loopVars.isEmpty()) {
|
||||
if (loopInputs != null) {
|
||||
if (loopVars != null && !loopVars.isEmpty()) {
|
||||
addIssue(
|
||||
issues,
|
||||
issueKeys,
|
||||
"LOOP_INPUT_SCHEMA_CONFLICT",
|
||||
"循环输入配置存在冲突,请重新保存循环节点",
|
||||
node.id,
|
||||
null,
|
||||
node.name);
|
||||
}
|
||||
JSONObject count = loopInputs.getJSONObject("count");
|
||||
JSONObject items = loopInputs.getJSONObject("items");
|
||||
if (count == null && items == null) {
|
||||
addIssue(
|
||||
issues,
|
||||
issueKeys,
|
||||
"LOOP_INPUT_REQUIRED",
|
||||
"请至少配置循环次数或输入数组",
|
||||
node.id,
|
||||
null,
|
||||
node.name);
|
||||
return;
|
||||
}
|
||||
checkExplicitCountParameter(count, node, issues, issueKeys);
|
||||
checkExplicitItemsParameter(items, node, issues, issueKeys);
|
||||
return;
|
||||
}
|
||||
|
||||
if (loopVars == null || loopVars.isEmpty()) {
|
||||
addIssue(
|
||||
issues,
|
||||
issueKeys,
|
||||
"LOOP_INPUT_REQUIRED",
|
||||
"请至少配置循环次数或输入数组",
|
||||
node.id,
|
||||
null,
|
||||
node.name);
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject loopVar = loopVars.getJSONObject(0);
|
||||
if (loopVar == null || !"fixed".equals(loopVar.getString("refType"))) {
|
||||
if (loopVar == null) {
|
||||
addIssue(
|
||||
issues,
|
||||
issueKeys,
|
||||
"LOOP_INPUT_REQUIRED",
|
||||
"请至少配置循环次数或输入数组",
|
||||
node.id,
|
||||
null,
|
||||
node.name);
|
||||
return;
|
||||
}
|
||||
if (!"fixed".equals(loopVar.getString("refType"))) {
|
||||
return;
|
||||
}
|
||||
Object value = loopVar.get("value");
|
||||
if (value != null && StringUtils.hasText(String.valueOf(value))) {
|
||||
if (value == null || !StringUtils.hasText(String.valueOf(value))) {
|
||||
addIssue(
|
||||
issues,
|
||||
issueKeys,
|
||||
"EXPLICIT_LOOP_COUNT_INVALID",
|
||||
"循环次数必须是 1~300 的整数",
|
||||
node.id,
|
||||
null,
|
||||
node.name);
|
||||
return;
|
||||
}
|
||||
addLoopCountIssueIfInvalid(
|
||||
value,
|
||||
"EXPLICIT_LOOP_COUNT_INVALID",
|
||||
node,
|
||||
issues,
|
||||
issueKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验显式循环次数参数。
|
||||
*
|
||||
* @param count 次数参数
|
||||
* @param node 节点
|
||||
* @param issues 问题列表
|
||||
* @param issueKeys 问题去重键
|
||||
*/
|
||||
private void checkExplicitCountParameter(
|
||||
JSONObject count,
|
||||
NodeView node,
|
||||
List<WorkflowCheckIssue> issues,
|
||||
Set<String> issueKeys) {
|
||||
if (count == null) {
|
||||
return;
|
||||
}
|
||||
String refType = trimToNull(count.getString("refType"));
|
||||
if ("fixed".equals(refType)) {
|
||||
Object value = count.get("value");
|
||||
if (value == null || !StringUtils.hasText(String.valueOf(value))) {
|
||||
addIssue(
|
||||
issues,
|
||||
issueKeys,
|
||||
"EXPLICIT_LOOP_COUNT_INVALID",
|
||||
"循环次数必须是 1~300 的整数",
|
||||
node.id,
|
||||
null,
|
||||
node.name);
|
||||
return;
|
||||
}
|
||||
addLoopCountIssueIfInvalid(
|
||||
value,
|
||||
"EXPLICIT_LOOP_COUNT_INVALID",
|
||||
node,
|
||||
issues,
|
||||
issueKeys);
|
||||
return;
|
||||
}
|
||||
if (!"ref".equals(refType)
|
||||
|| !StringUtils.hasText(trimToNull(count.getString("ref")))
|
||||
|| !"Number".equalsIgnoreCase(
|
||||
safe(count.getString("dataType")))) {
|
||||
addIssue(
|
||||
issues,
|
||||
issueKeys,
|
||||
"EXPLICIT_LOOP_COUNT_INVALID",
|
||||
"循环次数必须引用数值变量",
|
||||
node.id,
|
||||
null,
|
||||
node.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验显式循环数组参数。
|
||||
*
|
||||
* @param items 数组参数
|
||||
* @param node 节点
|
||||
* @param issues 问题列表
|
||||
* @param issueKeys 问题去重键
|
||||
*/
|
||||
private void checkExplicitItemsParameter(
|
||||
JSONObject items,
|
||||
NodeView node,
|
||||
List<WorkflowCheckIssue> issues,
|
||||
Set<String> issueKeys) {
|
||||
if (items == null) {
|
||||
return;
|
||||
}
|
||||
String refType = trimToNull(items.getString("refType"));
|
||||
String ref = trimToNull(items.getString("ref"));
|
||||
String dataType = trimToNull(items.getString("dataType"));
|
||||
if (!"ref".equals(refType)
|
||||
|| !StringUtils.hasText(ref)
|
||||
|| !isArrayDataType(dataType)) {
|
||||
addIssue(
|
||||
issues,
|
||||
issueKeys,
|
||||
"EXPLICIT_LOOP_ITEMS_TYPE_INVALID",
|
||||
"输入数组必须引用数组变量",
|
||||
node.id,
|
||||
null,
|
||||
node.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数类型是否为数组。
|
||||
*
|
||||
* @param dataType 参数类型
|
||||
* @return 数组类型返回 {@code true}
|
||||
*/
|
||||
private boolean isArrayDataType(String dataType) {
|
||||
return StringUtils.hasText(dataType)
|
||||
&& ("Array".equalsIgnoreCase(dataType)
|
||||
|| dataType.regionMatches(
|
||||
true, 0, "Array<", 0, "Array<".length()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 index 和 loopItem 仅在所属循环体内引用。
|
||||
*
|
||||
* @param node 当前节点
|
||||
* @param nodeMap 节点索引
|
||||
* @param issues 问题列表
|
||||
* @param issueKeys 问题去重键
|
||||
*/
|
||||
private void checkLoopVariableScope(
|
||||
NodeView node,
|
||||
Map<String, NodeView> nodeMap,
|
||||
List<WorkflowCheckIssue> issues,
|
||||
Set<String> issueKeys) {
|
||||
if (node.data == null) {
|
||||
return;
|
||||
}
|
||||
Set<String> references = new LinkedHashSet<>();
|
||||
collectParameterReferences(node.data, references);
|
||||
for (String reference : references) {
|
||||
int separator = reference.indexOf('.');
|
||||
if (separator <= 0) {
|
||||
continue;
|
||||
}
|
||||
String loopNodeId = reference.substring(0, separator);
|
||||
String variablePath = reference.substring(separator + 1);
|
||||
if (!("index".equals(variablePath)
|
||||
|| variablePath.startsWith("index.")
|
||||
|| "loopItem".equals(variablePath)
|
||||
|| variablePath.startsWith("loopItem."))) {
|
||||
continue;
|
||||
}
|
||||
NodeView loopNode = nodeMap.get(loopNodeId);
|
||||
if (loopNode == null || !TYPE_LOOP.equals(loopNode.type)) {
|
||||
continue;
|
||||
}
|
||||
if (!isDescendantOfLoop(node, loopNodeId, nodeMap)) {
|
||||
addIssue(
|
||||
issues,
|
||||
issueKeys,
|
||||
"LOOP_SCOPE_REFERENCE_INVALID",
|
||||
"循环变量只能在对应循环体内使用",
|
||||
node.id,
|
||||
null,
|
||||
node.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集节点数据中的参数引用。
|
||||
*
|
||||
* @param value 待遍历值
|
||||
* @param references 引用结果
|
||||
*/
|
||||
private void collectParameterReferences(
|
||||
Object value, Set<String> references) {
|
||||
if (value instanceof JSONObject object) {
|
||||
String reference = trimToNull(object.getString("ref"));
|
||||
if (StringUtils.hasText(reference)) {
|
||||
references.add(reference);
|
||||
}
|
||||
for (Object child : object.values()) {
|
||||
collectParameterReferences(child, references);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (value instanceof JSONArray array) {
|
||||
for (Object child : array) {
|
||||
collectParameterReferences(child, references);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断节点是否位于指定循环节点内部。
|
||||
*
|
||||
* @param node 当前节点
|
||||
* @param loopNodeId 循环节点 ID
|
||||
* @param nodeMap 节点索引
|
||||
* @return 位于循环体内返回 {@code true}
|
||||
*/
|
||||
private boolean isDescendantOfLoop(
|
||||
NodeView node,
|
||||
String loopNodeId,
|
||||
Map<String, NodeView> nodeMap) {
|
||||
Set<String> visited = new HashSet<>();
|
||||
NodeView current = node;
|
||||
while (current != null
|
||||
&& StringUtils.hasText(current.parentId)
|
||||
&& visited.add(current.id)) {
|
||||
if (loopNodeId.equals(current.parentId)) {
|
||||
return true;
|
||||
}
|
||||
current = nodeMap.get(current.parentId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package tech.easyflow.ai.easyagentsflow.knowledge;
|
||||
|
||||
import com.easyagents.core.document.Document;
|
||||
import com.easyagents.flow.core.knowledge.Knowledge;
|
||||
import com.easyagents.flow.core.node.KnowledgeNode;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import tech.easyflow.ai.rag.KnowledgeRetrievalRequest;
|
||||
import tech.easyflow.ai.service.DocumentCollectionService;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link KnowledgeProviderImpl} 的工作流文档契约测试。
|
||||
*/
|
||||
public class KnowledgeProviderImplTest {
|
||||
|
||||
/**
|
||||
* 验证对象数组同时提供稳定顶层字段和旧版字段。
|
||||
*
|
||||
* @throws Exception 注入测试依赖失败时抛出
|
||||
*/
|
||||
@Test
|
||||
public void shouldExposeStableDocumentFieldsAndKeepLegacyFields()
|
||||
throws Exception {
|
||||
Document document = new Document();
|
||||
document.setId(BigInteger.valueOf(42));
|
||||
document.setTitle("文档标题");
|
||||
document.setContent("文档内容");
|
||||
document.addMetadata("documentId", BigInteger.valueOf(420));
|
||||
document.addMetadata("legacyKey", "legacy-value");
|
||||
|
||||
DocumentCollectionService service =
|
||||
mock(DocumentCollectionService.class);
|
||||
when(service.search(any(KnowledgeRetrievalRequest.class)))
|
||||
.thenReturn(Collections.singletonList(document));
|
||||
KnowledgeProviderImpl provider = new KnowledgeProviderImpl();
|
||||
setField(provider, "documentCollectionService", service);
|
||||
|
||||
KnowledgeNode knowledgeNode = new KnowledgeNode();
|
||||
knowledgeNode.setId("knowledge-node");
|
||||
Knowledge knowledge =
|
||||
provider.getKnowledge(BigInteger.valueOf(88));
|
||||
List<Map<String, Object>> result =
|
||||
knowledge.search("问题", 10, knowledgeNode, null);
|
||||
|
||||
Assert.assertEquals(1, result.size());
|
||||
Map<String, Object> item = result.get(0);
|
||||
Assert.assertEquals("文档标题", item.get("title"));
|
||||
Assert.assertEquals("文档内容", item.get("content"));
|
||||
Assert.assertEquals(BigInteger.valueOf(420), item.get("documentId"));
|
||||
Assert.assertEquals(BigInteger.valueOf(88), item.get("knowledgeId"));
|
||||
Assert.assertEquals(42L, ((Number) item.get("id")).longValue());
|
||||
Assert.assertTrue(item.containsKey("metadataMap"));
|
||||
Assert.assertEquals(
|
||||
"legacy-value",
|
||||
((Map<?, ?>) item.get("metadataMap")).get("legacyKey"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入测试依赖。
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param fieldName 字段名称
|
||||
* @param value 字段值
|
||||
* @throws Exception 字段不存在或不可访问时抛出
|
||||
*/
|
||||
private static void setField(
|
||||
Object target, String fieldName, Object value) throws Exception {
|
||||
Field field = KnowledgeProviderImpl.class
|
||||
.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,134 @@ public class WorkflowCheckServiceTest {
|
||||
assertHasCode(result, "EXPLICIT_LOOP_COUNT_INVALID");
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证循环次数和输入数组可分别存在,也可同时存在。
|
||||
*/
|
||||
@Test
|
||||
public void testSaveShouldPassForSupportedExplicitLoopInputs() throws Exception {
|
||||
WorkflowCheckService service = newService(new HashMap<>());
|
||||
JSONObject countOnly = loopData(
|
||||
fixedParameter("count", "3", "Number"), null);
|
||||
JSONObject itemsOnly = loopData(
|
||||
null, refParameter("items", "start.items", "Array<Object>"));
|
||||
JSONObject both = loopData(
|
||||
refParameter("count", "start.count", "Number"),
|
||||
refParameter("items", "knowledge.documents", "Array"));
|
||||
String content = workflowJson(
|
||||
array(
|
||||
node("loop-count", "loopNode", null, countOnly),
|
||||
node("loop-items", "loopNode", null, itemsOnly),
|
||||
node("loop-both", "loopNode", null, both)
|
||||
),
|
||||
new JSONArray());
|
||||
|
||||
WorkflowCheckResult result = service.checkContent(
|
||||
content, WorkflowCheckStage.SAVE, null);
|
||||
|
||||
Assert.assertTrue(result.isPassed());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证显式循环至少需要一个输入。
|
||||
*/
|
||||
@Test
|
||||
public void testSaveShouldBlockEmptyExplicitLoopInputs() throws Exception {
|
||||
WorkflowCheckService service = newService(new HashMap<>());
|
||||
JSONObject loopData = data("循环");
|
||||
loopData.put("loopInputs", new JSONObject());
|
||||
String content = workflowJson(
|
||||
array(node("loop-1", "loopNode", null, loopData)),
|
||||
new JSONArray());
|
||||
|
||||
WorkflowCheckResult result = service.checkContent(
|
||||
content, WorkflowCheckStage.SAVE, null);
|
||||
|
||||
Assert.assertFalse(result.isPassed());
|
||||
assertHasCode(result, "LOOP_INPUT_REQUIRED");
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证输入数组只接受数组变量引用。
|
||||
*/
|
||||
@Test
|
||||
public void testSaveShouldBlockFixedOrNonArrayLoopItems() throws Exception {
|
||||
WorkflowCheckService service = newService(new HashMap<>());
|
||||
JSONObject fixedItems = loopData(
|
||||
null, fixedParameter("items", "[]", "Array"));
|
||||
JSONObject stringItems = loopData(
|
||||
null, refParameter("items", "start.value", "String"));
|
||||
String content = workflowJson(
|
||||
array(
|
||||
node("loop-fixed", "loopNode", null, fixedItems),
|
||||
node("loop-string", "loopNode", null, stringItems)
|
||||
),
|
||||
new JSONArray());
|
||||
|
||||
WorkflowCheckResult result = service.checkContent(
|
||||
content, WorkflowCheckStage.SAVE, null);
|
||||
|
||||
Assert.assertFalse(result.isPassed());
|
||||
assertHasCode(result, "EXPLICIT_LOOP_ITEMS_TYPE_INVALID");
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证新旧循环输入不能同时提交。
|
||||
*/
|
||||
@Test
|
||||
public void testSaveShouldBlockConflictingLoopInputSchemas() throws Exception {
|
||||
WorkflowCheckService service = newService(new HashMap<>());
|
||||
JSONObject loopData = loopData(
|
||||
fixedParameter("count", "2", "Number"), null);
|
||||
loopData.put("loopVars", array(
|
||||
fixedParameter("loopVar", "2", "Number")));
|
||||
String content = workflowJson(
|
||||
array(node("loop-1", "loopNode", null, loopData)),
|
||||
new JSONArray());
|
||||
|
||||
WorkflowCheckResult result = service.checkContent(
|
||||
content, WorkflowCheckStage.SAVE, null);
|
||||
|
||||
Assert.assertFalse(result.isPassed());
|
||||
assertHasCode(result, "LOOP_INPUT_SCHEMA_CONFLICT");
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证循环变量只能由对应循环体中的节点引用。
|
||||
*/
|
||||
@Test
|
||||
public void testSaveShouldBlockLoopVariableReferenceOutsideScope() throws Exception {
|
||||
WorkflowCheckService service = newService(new HashMap<>());
|
||||
JSONObject childData = data("循环内节点");
|
||||
childData.put("inputDefs", array(
|
||||
refParameter("item", "loop-1.loopItem.content", "String")));
|
||||
JSONObject outsideData = data("循环外节点");
|
||||
outsideData.put("inputDefs", array(
|
||||
refParameter("index", "loop-1.index", "Number")));
|
||||
String content = workflowJson(
|
||||
array(
|
||||
node(
|
||||
"loop-1",
|
||||
"loopNode",
|
||||
null,
|
||||
loopData(fixedParameter(
|
||||
"count", "2", "Number"), null)),
|
||||
node("inside", "codeNode", "loop-1", childData),
|
||||
node("outside", "codeNode", null, outsideData)
|
||||
),
|
||||
new JSONArray());
|
||||
|
||||
WorkflowCheckResult result = service.checkContent(
|
||||
content, WorkflowCheckStage.SAVE, null);
|
||||
|
||||
Assert.assertFalse(result.isPassed());
|
||||
Assert.assertEquals(
|
||||
1,
|
||||
result.getIssues().stream()
|
||||
.filter(issue -> "LOOP_SCOPE_REFERENCE_INVALID"
|
||||
.equals(issue.getCode()))
|
||||
.count());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证嵌套节点只能挂在显式循环节点下。
|
||||
*/
|
||||
@@ -552,6 +680,62 @@ public class WorkflowCheckServiceTest {
|
||||
return node(id, "search-dataset-node", parentId, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建显式循环节点数据。
|
||||
*
|
||||
* @param count 次数参数
|
||||
* @param items 数组参数
|
||||
* @return 循环节点数据
|
||||
*/
|
||||
private static JSONObject loopData(JSONObject count, JSONObject items) {
|
||||
JSONObject data = data("循环");
|
||||
JSONObject inputs = new JSONObject();
|
||||
if (count != null) {
|
||||
inputs.put("count", count);
|
||||
}
|
||||
if (items != null) {
|
||||
inputs.put("items", items);
|
||||
}
|
||||
data.put("loopInputs", inputs);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建固定值参数。
|
||||
*
|
||||
* @param name 参数名称
|
||||
* @param value 参数值
|
||||
* @param dataType 参数类型
|
||||
* @return 参数对象
|
||||
*/
|
||||
private static JSONObject fixedParameter(
|
||||
String name, String value, String dataType) {
|
||||
JSONObject parameter = new JSONObject();
|
||||
parameter.put("name", name);
|
||||
parameter.put("refType", "fixed");
|
||||
parameter.put("value", value);
|
||||
parameter.put("dataType", dataType);
|
||||
return parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建变量引用参数。
|
||||
*
|
||||
* @param name 参数名称
|
||||
* @param ref 引用路径
|
||||
* @param dataType 参数类型
|
||||
* @return 参数对象
|
||||
*/
|
||||
private static JSONObject refParameter(
|
||||
String name, String ref, String dataType) {
|
||||
JSONObject parameter = new JSONObject();
|
||||
parameter.put("name", name);
|
||||
parameter.put("refType", "ref");
|
||||
parameter.put("ref", ref);
|
||||
parameter.put("dataType", dataType);
|
||||
return parameter;
|
||||
}
|
||||
|
||||
private static JSONObject data(String title) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("title", title);
|
||||
|
||||
Reference in New Issue
Block a user