Files
FastGPT/projects/app/test/cases/web/workflow/utils.test.ts
Archer 33d40fd077 feature: System plugin (#5131)
* feat: system Tool (#4959)

* feat: independent system tool

* chore: use ToolNode instead of PluginModule

* chore: tools

* chore: tools templateDir

* refactor: templates

* feat: flush code

* chore: update template

* refactor: migrate delay

* feat: worker pool

* chore: Dockerfile

* docs:  add tools.template.json

* feat: auto flush system tools

* fix: ts error

* chore: create new pool temporarily

* chore: system tool migration

* chore: migration

* fix: fix pnpm-workspace.yaml

* chore: update pnpm-lock.yaml to integrate tool

* chore(systemTool): chore

* chore: add system plugin

* chore(deps): update @fastgpt-sdk/plugin

* fix: type error

* chore: remove plugin package

* chore: move pro plugins code to open source

* feat: support system tool config input

* fix: type error

* perf: i18n

* fix: cr

* chore: update sdk

* feat: system plugin cache

* update mcp server (#5076)

* update mcp server

* fix: action

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile

* feat: system Tool (#4959)

* feat: independent system tool

* chore: use ToolNode instead of PluginModule

* chore: tools

* chore: tools templateDir

* refactor: templates

* feat: flush code

* chore: update template

* refactor: migrate delay

* feat: worker pool

* chore: Dockerfile

* docs:  add tools.template.json

* feat: auto flush system tools

* fix: ts error

* chore: create new pool temporarily

* chore: system tool migration

* chore: migration

* fix: fix pnpm-workspace.yaml

* chore: update pnpm-lock.yaml to integrate tool

* chore(systemTool): chore

* chore: add system plugin

* chore(deps): update @fastgpt-sdk/plugin

* fix: type error

* chore: remove plugin package

* chore: move pro plugins code to open source

* feat: support system tool config input

* fix: type error

* perf: i18n

* fix: cr

* chore: update sdk

* feat: system plugin cache

* perf: run tool

* update package

* perf: config key

* fix: tool ini

* tool config params

* perf: workflow type

* rename tools to  agent

* version list

* perf: tool error

* config secret ux

* perf: config secret ux

* fix: tool config field

* add course to secret input

* feat: support inputConfig switch (#5099)

* feat: support inputConfig switch

* deps: update @fastgpt-sdk/plugin

* chore: update workflows

* fix: inputType

* fix: secret

* add default value to node

* update i18n

* eslint

* add precision to number input

* feat: add number input and select

* perf: number ux

* fix: code

* Proxies image requests to plugin service (#5111)

* Proxies image requests to plugin service

Adds a rewrite rule and API endpoint to proxy image requests
to the plugin service. This allows the app to fetch images from
the plugin's tools directory.

It also adds the plugin base URL to the service's constants, so that
it can use the plugin URL when proxying requests.

* fix: update FastGPTPluginUrl to remove unnecessary API path

* feat: update image proxy destination and add plugin image handler

* Adapt plugin id

* replace avatar

* remove rewrite

* fix: plugin avatar

* update system tool doc

* feat: system tool type

* yml sh

* yml sh

* update doc

* fix: simple app tool select

* fix: switch ui

* update pacakge

* Yamljs (#5129)

* update docker-compose configuration: bump fastgpt and fastgpt-plugin images, change minio host to service name, and adjust service dependencies

* refactor: comment out port exposure in docker-compose configuration

* update: uncomment port exposure in docker-compose configuration

* update: change MINIO_HOST to use specific IP address in docker configuration

* update: modify fastgpt-plugin image version in docker configuration

* update readme

* doc

* remove

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
Co-authored-by: Theresa <63280168+sd0ric4@users.noreply.github.com>
2025-07-02 18:15:00 +08:00

240 lines
6.2 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
} 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();
});
});