/** * Integration Test: Tool Data Processing Pipeline * * Tests the integration between tool utility functions and type conversions. * Verifies that data flows correctly through the processing pipeline: * raw API data → form schemas → form values → configured values. */ import { describe, expect, it } from 'vitest' import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils/index' import { addDefaultValue, generateFormValue, getConfiguredValue, getPlainValue, getStructureValue, toolCredentialToFormSchemas, toolParametersToFormSchemas, toType, triggerEventParametersToFormSchemas, } from '@/app/components/tools/utils/to-form-schema' describe('Tool Data Processing Pipeline Integration', () => { describe('End-to-end: API schema → form schema → form value', () => { it('processes tool parameters through the full pipeline', () => { const rawParameters = [ { name: 'query', label: { en_US: 'Search Query', zh_Hans: '搜索查询' }, type: 'string', required: true, default: 'hello', form: 'llm', human_description: { en_US: 'Enter your search query', zh_Hans: '输入搜索查询' }, llm_description: 'The search query string', options: [], }, { name: 'limit', label: { en_US: 'Result Limit', zh_Hans: '结果限制' }, type: 'number', required: false, default: '10', form: 'form', human_description: { en_US: 'Maximum results', zh_Hans: '最大结果数' }, llm_description: 'Limit for results', options: [], }, ] const formSchemas = toolParametersToFormSchemas(rawParameters as unknown as Parameters[0]) expect(formSchemas).toHaveLength(2) expect(formSchemas[0].variable).toBe('query') expect(formSchemas[0].required).toBe(true) expect(formSchemas[0].type).toBe('text-input') expect(formSchemas[1].variable).toBe('limit') expect(formSchemas[1].type).toBe('number-input') const withDefaults = addDefaultValue({}, formSchemas) expect(withDefaults.query).toBe('hello') expect(withDefaults.limit).toBe('10') const formValues = generateFormValue({}, formSchemas, false) expect(formValues).toBeDefined() expect(formValues.query).toBeDefined() expect(formValues.limit).toBeDefined() }) it('processes tool credentials through the pipeline', () => { const rawCredentials = [ { name: 'api_key', label: { en_US: 'API Key', zh_Hans: 'API 密钥' }, type: 'secret-input', required: true, default: '', placeholder: { en_US: 'Enter API key', zh_Hans: '输入 API 密钥' }, help: { en_US: 'Your API key', zh_Hans: '你的 API 密钥' }, url: 'https://example.com/get-key', options: [], }, ] const credentialSchemas = toolCredentialToFormSchemas(rawCredentials as Parameters[0]) expect(credentialSchemas).toHaveLength(1) expect(credentialSchemas[0].variable).toBe('api_key') expect(credentialSchemas[0].required).toBe(true) expect(credentialSchemas[0].type).toBe('secret-input') }) it('processes trigger event parameters through the pipeline', () => { const rawParams = [ { name: 'event_type', label: { en_US: 'Event Type', zh_Hans: '事件类型' }, type: 'select', required: true, default: 'push', form: 'form', description: { en_US: 'Type of event', zh_Hans: '事件类型' }, options: [ { value: 'push', label: { en_US: 'Push', zh_Hans: '推送' } }, { value: 'pull', label: { en_US: 'Pull', zh_Hans: '拉取' } }, ], }, ] const schemas = triggerEventParametersToFormSchemas(rawParams as unknown as Parameters[0]) expect(schemas).toHaveLength(1) expect(schemas[0].name).toBe('event_type') expect(schemas[0].type).toBe('select') expect(schemas[0].options).toHaveLength(2) }) }) describe('Type conversion integration', () => { it('converts all supported types correctly', () => { const typeConversions = [ { input: 'string', expected: 'text-input' }, { input: 'number', expected: 'number-input' }, { input: 'boolean', expected: 'checkbox' }, { input: 'select', expected: 'select' }, { input: 'secret-input', expected: 'secret-input' }, { input: 'file', expected: 'file' }, { input: 'files', expected: 'files' }, ] typeConversions.forEach(({ input, expected }) => { expect(toType(input)).toBe(expected) }) }) it('returns the original type for unrecognized types', () => { expect(toType('unknown-type')).toBe('unknown-type') expect(toType('app-selector')).toBe('app-selector') }) }) describe('Value extraction integration', () => { it('wraps values with getStructureValue and extracts inner value with getPlainValue', () => { const plainInput = { query: 'test', limit: 10 } const structured = getStructureValue(plainInput) expect(structured.query).toEqual({ value: 'test' }) expect(structured.limit).toEqual({ value: 10 }) const objectStructured = { query: { value: { type: 'constant', content: 'test search' } }, limit: { value: { type: 'constant', content: 10 } }, } const extracted = getPlainValue(objectStructured) expect(extracted.query).toEqual({ type: 'constant', content: 'test search' }) expect(extracted.limit).toEqual({ type: 'constant', content: 10 }) }) it('handles getConfiguredValue for workflow tool configurations', () => { const formSchemas = [ { variable: 'query', type: 'text-input', default: 'default-query' }, { variable: 'format', type: 'select', default: 'json' }, ] const configured = getConfiguredValue({}, formSchemas) expect(configured).toBeDefined() expect(configured.query).toBeDefined() expect(configured.format).toBeDefined() }) it('preserves existing values in getConfiguredValue', () => { const formSchemas = [ { variable: 'query', type: 'text-input', default: 'default-query' }, ] const configured = getConfiguredValue({ query: 'my-existing-query' }, formSchemas) expect(configured.query).toBe('my-existing-query') }) }) describe('Agent utilities integration', () => { it('sorts agent thoughts and enriches with file infos end-to-end', () => { const thoughts = [ { id: 't3', position: 3, tool: 'search', files: ['f1'] }, { id: 't1', position: 1, tool: 'analyze', files: [] }, { id: 't2', position: 2, tool: 'summarize', files: ['f2'] }, ] as Parameters[0] const messageFiles = [ { id: 'f1', name: 'result.txt', type: 'document' }, { id: 'f2', name: 'summary.pdf', type: 'document' }, ] as Parameters[1] const sorted = sortAgentSorts(thoughts) expect(sorted[0].id).toBe('t1') expect(sorted[1].id).toBe('t2') expect(sorted[2].id).toBe('t3') const enriched = addFileInfos(sorted, messageFiles) expect(enriched[0].message_files).toBeUndefined() expect(enriched[1].message_files).toHaveLength(1) expect(enriched[1].message_files![0].id).toBe('f2') expect(enriched[2].message_files).toHaveLength(1) expect(enriched[2].message_files![0].id).toBe('f1') }) it('handles null inputs gracefully in the pipeline', () => { const sortedNull = sortAgentSorts(null as never) expect(sortedNull).toBeNull() const enrichedNull = addFileInfos(null as never, []) expect(enrichedNull).toBeNull() // addFileInfos with empty list and null files returns the mapped (empty) list const enrichedEmptyList = addFileInfos([], null as never) expect(enrichedEmptyList).toEqual([]) }) }) describe('Default value application', () => { it('applies defaults only to empty fields, preserving user values', () => { const userValues = { api_key: 'user-provided-key' } const schemas = [ { variable: 'api_key', type: 'text-input', default: 'default-key', name: 'api_key' }, { variable: 'secret', type: 'secret-input', default: 'default-secret', name: 'secret' }, ] const result = addDefaultValue(userValues, schemas) expect(result.api_key).toBe('user-provided-key') expect(result.secret).toBe('default-secret') }) it('handles boolean type conversion in defaults', () => { const schemas = [ { variable: 'enabled', type: 'boolean', default: 'true', name: 'enabled' }, ] const result = addDefaultValue({ enabled: 'true' }, schemas) expect(result.enabled).toBe(true) }) }) })