Files
dify/web/app/components/workflow-app/__tests__/utils.spec.ts
2026-04-24 10:27:55 +08:00

148 lines
4.4 KiB
TypeScript

import type { Node } from '@/app/components/workflow/types'
import { BlockEnum, SupportUploadFileTypes } from '@/app/components/workflow/types'
import { TransferMethod } from '@/types/app'
import {
buildInitialFeatures,
buildTriggerStatusMap,
coerceReplayUserInputs,
normalizeWorkflowNodesForBackend,
normalizeWorkflowNodesForFrontend,
} from '../utils'
type HumanInputTestField = {
type: string
output_variable_name: string
}
type HumanInputTestNode = Node<{
inputs: HumanInputTestField[]
}>
describe('workflow-app utils', () => {
it('should map trigger statuses to enabled and disabled states', () => {
expect(buildTriggerStatusMap([
{ node_id: 'node-1', status: 'enabled' },
{ node_id: 'node-2', status: 'disabled' },
{ node_id: 'node-3', status: 'paused' },
])).toEqual({
'node-1': 'enabled',
'node-2': 'disabled',
'node-3': 'disabled',
})
})
it('should coerce replay run inputs, omit sys keys, and stringify complex values', () => {
expect(coerceReplayUserInputs({
'sys.query': 'hidden',
'query': 'hello',
'count': 3,
'enabled': true,
'nullable': null,
'metadata': { nested: true },
})).toEqual({
query: 'hello',
count: 3,
enabled: true,
nullable: '',
metadata: '{"nested":true}',
})
expect(coerceReplayUserInputs('invalid')).toBeNull()
expect(coerceReplayUserInputs(null)).toBeNull()
})
it('should normalize human-input multi-file types between frontend and backend payloads', () => {
const nodes: HumanInputTestNode[] = [
{
id: 'node-1',
type: 'custom',
position: { x: 0, y: 0 },
data: {
title: 'Human Input',
desc: '',
type: BlockEnum.HumanInput,
inputs: [
{ type: 'paragraph', output_variable_name: 'summary' },
{ type: 'file-list', output_variable_name: 'attachments' },
],
},
},
]
const backendNodes = normalizeWorkflowNodesForBackend(nodes) as HumanInputTestNode[]
expect(backendNodes[0]!.data.inputs).toEqual([
{ type: 'paragraph', output_variable_name: 'summary' },
{ type: 'file_list', output_variable_name: 'attachments' },
])
const frontendPayloadNodes: HumanInputTestNode[] = [
{
...nodes[0]!,
data: {
...nodes[0]!.data,
inputs: [
{ type: 'paragraph', output_variable_name: 'summary' },
{ type: 'file_list', output_variable_name: 'attachments' },
],
},
},
]
const frontendNodes = normalizeWorkflowNodesForFrontend(frontendPayloadNodes) as HumanInputTestNode[]
expect(frontendNodes[0]!.data.inputs).toEqual([
{ type: 'paragraph', output_variable_name: 'summary' },
{ type: 'file-list', output_variable_name: 'attachments' },
])
})
it('should build initial features with file-upload and feature fallbacks', () => {
const result = buildInitialFeatures({
file_upload: {
enabled: true,
allowed_file_types: [SupportUploadFileTypes.image],
allowed_file_extensions: ['.png'],
allowed_file_upload_methods: [TransferMethod.local_file],
number_limits: 2,
image: {
enabled: true,
number_limits: 5,
transfer_methods: [TransferMethod.remote_url],
},
},
opening_statement: 'hello',
suggested_questions: ['Q1'],
suggested_questions_after_answer: { enabled: true },
speech_to_text: { enabled: true },
text_to_speech: { enabled: true },
retriever_resource: { enabled: true },
sensitive_word_avoidance: { enabled: true },
}, { enabled: true } as never)
expect(result).toMatchObject({
file: {
enabled: true,
allowed_file_types: [SupportUploadFileTypes.image],
allowed_file_extensions: ['.png'],
allowed_file_upload_methods: [TransferMethod.local_file],
number_limits: 2,
fileUploadConfig: { enabled: true },
image: {
enabled: true,
number_limits: 5,
transfer_methods: [TransferMethod.remote_url],
},
},
opening: {
enabled: true,
opening_statement: 'hello',
suggested_questions: ['Q1'],
},
suggested: { enabled: true },
speech2text: { enabled: true },
text2speech: { enabled: true },
citation: { enabled: true },
moderation: { enabled: true },
})
})
})