Files
FastGPT/projects/app/test/cases/web/workflow/utils.test.ts
Archer 0ef3d40296 Test version (#4792)
* plugin node version select (#4760)

* plugin node version select

* type

* fix

* fix

* perf: version list

* fix node version (#4787)

* change my select

* fix-ui

* fix test

* add test

* fix

* remove invalid version field

* filter deprecated field

* fix: claude tool call

* fix: test

---------

Co-authored-by: heheer <heheer@sealos.io>
2025-05-12 22:27:01 +08:00

340 lines
9.1 KiB
TypeScript

import { vi, describe, it, expect, beforeEach } from 'vitest';
import type {
FlowNodeItemType,
FlowNodeTemplateType,
StoreNodeItemType
} from '@fastgpt/global/core/workflow/type/node';
import type { Node, Edge } from 'reactflow';
import {
FlowNodeTypeEnum,
FlowNodeInputTypeEnum,
FlowNodeOutputTypeEnum,
EDGE_TYPE
} from '@fastgpt/global/core/workflow/node/constant';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import {
nodeTemplate2FlowNode,
storeNode2FlowNode,
filterWorkflowNodeOutputsByType,
checkWorkflowNodeAndConnection,
getLatestNodeTemplate
} from '@/web/core/workflow/utils';
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io';
describe('nodeTemplate2FlowNode', () => {
it('should convert template to flow node', () => {
const template: FlowNodeTemplateType = {
id: 'template1',
templateType: 'formInput',
name: 'Test Node',
flowNodeType: FlowNodeTypeEnum.formInput,
inputs: [],
outputs: []
};
const result = nodeTemplate2FlowNode({
template,
position: { x: 100, y: 100 },
selected: true,
parentNodeId: 'parent1',
t: ((key: any) => key) as any
});
expect(result).toMatchObject({
type: FlowNodeTypeEnum.formInput,
position: { x: 100, y: 100 },
selected: true,
data: {
name: 'Test Node',
flowNodeType: FlowNodeTypeEnum.formInput,
parentNodeId: 'parent1'
}
});
expect(result.id).toBeDefined();
});
});
describe('storeNode2FlowNode', () => {
it('should convert store node to flow node', () => {
const storeNode: StoreNodeItemType = {
nodeId: 'node1',
flowNodeType: FlowNodeTypeEnum.formInput,
position: { x: 100, y: 100 },
inputs: [],
outputs: [],
name: 'Test Node',
version: '1.0'
};
const result = storeNode2FlowNode({
item: storeNode,
selected: true,
t: ((key: any) => key) as any
});
expect(result).toMatchObject({
id: 'node1',
type: FlowNodeTypeEnum.formInput,
position: { x: 100, y: 100 },
selected: true
});
});
it('should handle dynamic inputs and outputs', () => {
const storeNode: StoreNodeItemType = {
nodeId: 'node1',
flowNodeType: FlowNodeTypeEnum.formInput,
position: { x: 0, y: 0 },
inputs: [
{
key: 'dynamicInput',
label: 'Dynamic Input',
renderTypeList: [FlowNodeInputTypeEnum.addInputParam]
}
],
outputs: [
{
id: 'dynamicOutput',
key: 'dynamicOutput',
label: 'Dynamic Output',
type: FlowNodeOutputTypeEnum.dynamic
}
],
name: 'Test Node',
version: '1.0'
};
const result = storeNode2FlowNode({
item: storeNode,
t: ((key: any) => key) as any
});
expect(result.data.inputs).toHaveLength(3);
expect(result.data.outputs).toHaveLength(2);
});
// 这两个测试涉及到模拟冲突,请运行单独的测试文件:
// - utils.deprecated.test.ts: 测试 deprecated inputs/outputs
// - utils.version.test.ts: 测试 version 和 avatar inheritance
});
describe('filterWorkflowNodeOutputsByType', () => {
it('should filter outputs by type', () => {
const outputs: FlowNodeOutputItemType[] = [
{
id: '1',
valueType: WorkflowIOValueTypeEnum.string,
key: '1',
label: '1',
type: FlowNodeOutputTypeEnum.static
},
{
id: '2',
valueType: WorkflowIOValueTypeEnum.number,
key: '2',
label: '2',
type: FlowNodeOutputTypeEnum.static
},
{
id: '3',
valueType: WorkflowIOValueTypeEnum.boolean,
key: '3',
label: '3',
type: FlowNodeOutputTypeEnum.static
}
];
const result = filterWorkflowNodeOutputsByType(outputs, WorkflowIOValueTypeEnum.string);
expect(result).toHaveLength(1);
expect(result[0].id).toBe('1');
});
it('should return all outputs for any type', () => {
const outputs: FlowNodeOutputItemType[] = [
{
id: '1',
valueType: WorkflowIOValueTypeEnum.string,
key: '1',
label: '1',
type: FlowNodeOutputTypeEnum.static
},
{
id: '2',
valueType: WorkflowIOValueTypeEnum.number,
key: '2',
label: '2',
type: FlowNodeOutputTypeEnum.static
}
];
const result = filterWorkflowNodeOutputsByType(outputs, WorkflowIOValueTypeEnum.any);
expect(result).toHaveLength(2);
});
it('should handle array types correctly', () => {
const outputs: FlowNodeOutputItemType[] = [
{
id: '1',
valueType: WorkflowIOValueTypeEnum.string,
key: '1',
label: '1',
type: FlowNodeOutputTypeEnum.static
},
{
id: '2',
valueType: WorkflowIOValueTypeEnum.arrayString,
key: '2',
label: '2',
type: FlowNodeOutputTypeEnum.static
}
];
const result = filterWorkflowNodeOutputsByType(outputs, WorkflowIOValueTypeEnum.arrayString);
expect(result).toHaveLength(2);
});
});
describe('checkWorkflowNodeAndConnection', () => {
it('should validate nodes and connections', () => {
const nodes: Node[] = [
{
id: 'node1',
type: FlowNodeTypeEnum.formInput,
data: {
nodeId: 'node1',
flowNodeType: FlowNodeTypeEnum.formInput,
inputs: [
{
key: NodeInputKeyEnum.aiChatDatasetQuote,
required: true,
value: undefined,
renderTypeList: [FlowNodeInputTypeEnum.input]
}
],
outputs: []
},
position: { x: 0, y: 0 }
}
];
const edges: Edge[] = [
{
id: 'edge1',
source: 'node1',
target: 'node2',
type: EDGE_TYPE
}
];
const result = checkWorkflowNodeAndConnection({ nodes, edges });
expect(result).toEqual(['node1']);
});
it('should handle empty nodes and edges', () => {
const result = checkWorkflowNodeAndConnection({ nodes: [], edges: [] });
expect(result).toBeUndefined();
});
});
describe('getLatestNodeTemplate', () => {
it('should update node to latest template version', () => {
const node: FlowNodeItemType = {
id: 'node1',
nodeId: 'node1',
templateType: 'formInput',
flowNodeType: FlowNodeTypeEnum.formInput,
inputs: [
{
key: 'input1',
value: 'test',
renderTypeList: [FlowNodeInputTypeEnum.input],
label: 'Input 1'
}
],
outputs: [
{
key: 'output1',
value: 'test',
type: FlowNodeOutputTypeEnum.static,
label: 'Output 1',
id: 'output1'
}
],
name: 'Old Name',
intro: 'Old Intro'
};
const template: FlowNodeTemplateType = {
name: 'Template 1',
id: 'template1',
templateType: 'formInput',
flowNodeType: FlowNodeTypeEnum.formInput,
inputs: [
{ key: 'input1', renderTypeList: [FlowNodeInputTypeEnum.input], label: 'Input 1' },
{ key: 'input2', renderTypeList: [FlowNodeInputTypeEnum.input], label: 'Input 2' }
],
outputs: [
{ id: 'output1', key: 'output1', type: FlowNodeOutputTypeEnum.static, label: 'Output 1' },
{ id: 'output2', key: 'output2', type: FlowNodeOutputTypeEnum.static, label: 'Output 2' }
]
};
const result = getLatestNodeTemplate(node, template);
expect(result.inputs).toHaveLength(2);
expect(result.outputs).toHaveLength(2);
expect(result.name).toBe('Old Name');
});
it('should preserve existing values when updating template', () => {
const node: FlowNodeItemType = {
id: 'node1',
nodeId: 'node1',
templateType: 'formInput',
flowNodeType: FlowNodeTypeEnum.formInput,
inputs: [
{
key: 'input1',
value: 'existingValue',
renderTypeList: [FlowNodeInputTypeEnum.input],
label: 'Input 1'
}
],
outputs: [
{
key: 'output1',
value: 'existingOutput',
type: FlowNodeOutputTypeEnum.static,
label: 'Output 1',
id: 'output1'
}
],
name: 'Node Name',
intro: 'Node Intro'
};
const template: FlowNodeTemplateType = {
name: 'Template 1',
id: 'template1',
templateType: 'formInput',
flowNodeType: FlowNodeTypeEnum.formInput,
inputs: [
{ key: 'input1', renderTypeList: [FlowNodeInputTypeEnum.input], label: 'Input 1' },
{ key: 'input2', renderTypeList: [FlowNodeInputTypeEnum.input], label: 'Input 2' }
],
outputs: [
{ id: 'output1', key: 'output1', type: FlowNodeOutputTypeEnum.static, label: 'Output 1' },
{ id: 'output2', key: 'output2', type: FlowNodeOutputTypeEnum.static, label: 'Output 2' }
]
};
const result = getLatestNodeTemplate(node, template);
expect(result.inputs[0].value).toBe('existingValue');
expect(result.outputs[0].value).toBe('existingOutput');
});
});