diff --git a/executor/__tests__/executor.test.ts b/executor/__tests__/executor.test.ts deleted file mode 100644 index 139077495f..0000000000 --- a/executor/__tests__/executor.test.ts +++ /dev/null @@ -1,1089 +0,0 @@ -import { BlockOutput, ValueType } from '@/blocks/types' -import { SerializedWorkflow } from '@/serializer/types' -import { tools } from '@/tools' -import { Executor } from '../index' -import { Tool } from '../types' - -// Mock tools -const createMockTool = ( - id: string, - name: string, - mockResponse: any, - mockError?: string, - params: Record = {} -): Tool => ({ - id, - name, - description: 'Mock tool for testing', - version: '1.0.0', - params: { - input: { - type: 'string', - required: true, - description: 'Input to process', - }, - apiKey: { - type: 'string', - required: false, - description: 'API key for authentication', - default: 'test-key', - }, - ...params, - }, - request: { - url: 'https://api.test.com/endpoint', - method: 'POST', - headers: (params) => ({ - 'Content-Type': 'application/json', - Authorization: params.apiKey || 'test-key', - }), - body: (params) => ({ - input: params.input, - ...(params.optionalParam !== undefined ? { optionalParam: params.optionalParam } : {}), - }), - }, - transformResponse: async () => ({ - success: true, - output: { - text: mockResponse.result, - ...mockResponse.data, - }, - }), - transformError: () => mockError || 'Mock error', -}) - -jest.mock('@/tools', () => { - const toolsStore: Record = {} - return { - get tools() { - return toolsStore - }, - set tools(value) { - Object.keys(toolsStore).forEach((key) => delete toolsStore[key]) - Object.assign(toolsStore, value) - }, - executeTool: async (toolId: string, params: Record) => { - const tool = toolsStore[toolId] - if (!tool || !tool.request || !tool.transformResponse) { - throw new Error(`Tool not found: ${toolId}`) - } - - try { - // Mock the fetch call for test assertions - const url = - typeof tool.request.url === 'function' ? tool.request.url(params) : tool.request.url - const method = tool.request.method || 'POST' - const headers = - typeof tool.request.headers === 'function' - ? tool.request.headers(params) - : tool.request.headers || {} - const body = - typeof tool.request.body === 'function' ? tool.request.body(params) : tool.request.body - - const fetchResponse = await global.fetch(url as string, { - method, - headers, - body: JSON.stringify(body), - }) - - // Get the fetch response - const fetchResult = fetchResponse.ok - ? await fetchResponse.json() - : { success: false, error: 'API Error' } - - // If fetch failed, return error - if (!fetchResponse.ok || !fetchResult.success) { - return { - success: false, - error: tool.transformError ? tool.transformError(fetchResult) : 'API Error', - output: {}, - } - } - - // Return mocked response using the tool's transformResponse - const response = await tool.transformResponse({ - status: fetchResponse.status, - headers: fetchResponse.headers, - data: fetchResult.output || { result: params.input + ' processed', status: 200 }, - }) - - return { - success: true, - output: response.output, - } - } catch (error) { - return { - success: false, - error: tool.transformError ? tool.transformError(error) : 'Invalid type for input', - output: {}, - } - } - }, - } -}) - -describe('Executor', () => { - beforeEach(() => { - // Reset tools mock and fetch mock - ;(tools as any) = {} - global.fetch = jest.fn() - }) - - describe('Tool Execution', () => { - it('should execute a simple workflow with one tool', async () => { - const mockTool = createMockTool('test-tool', 'Test Tool', { - result: 'test processed', - data: { status: 200 }, - }) - ;(tools as any)['test-tool'] = mockTool - - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'block-1', - position: { x: 0, y: 0 }, - config: { - tool: 'test-tool', - params: { input: 'test' }, - }, - inputs: { input: 'string' }, - outputs: { - output: { - response: { - text: 'string', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [], - } - - // Mock fetch - global.fetch = jest.fn().mockImplementation(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ - success: true, - output: { - text: 'test processed', - status: 200, - }, - }), - }) - ) - - const executor = new Executor(workflow) - const result = await executor.execute('workflow-1') - - expect(result.success).toBe(true) - expect(result.output).toEqual({ - response: { - text: 'test processed', - status: 200, - }, - }) - expect(global.fetch).toHaveBeenCalledWith( - 'https://api.test.com/endpoint', - expect.objectContaining({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: 'test-key', - }, - body: JSON.stringify({ input: 'test' }), - }) - ) - }) - - it('should use default parameter values when not provided', async () => { - const mockTool = createMockTool( - 'test-tool', - 'Test Tool', - { result: 'test processed', data: { status: 200 } }, - undefined, - { - optionalParam: { - type: 'string', - required: false, - default: 'default-value', - }, - } - ) - ;(tools as any)['test-tool'] = mockTool - - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'block-1', - position: { x: 0, y: 0 }, - config: { - tool: 'test-tool', - params: { input: 'test' }, - }, - inputs: { input: 'string' }, - outputs: { - output: { - response: { - text: 'string', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [], - } - - global.fetch = jest.fn().mockImplementation(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ - success: true, - output: { - text: 'test processed', - status: 200, - }, - }), - }) - ) - - const executor = new Executor(workflow) - const result = await executor.execute('workflow-1') - - expect(result.success).toBe(true) - expect(global.fetch).toHaveBeenCalledWith( - 'https://api.test.com/endpoint', - expect.objectContaining({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: 'test-key', - }, - body: JSON.stringify({ - input: 'test', - optionalParam: 'default-value', - }), - }) - ) - }) - - it('should validate required parameters', async () => { - const mockTool = createMockTool('test-tool', 'Test Tool', { - result: 'test processed', - data: { status: 200 }, - }) - ;(tools as any)['test-tool'] = mockTool - - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'block-1', - position: { x: 0, y: 0 }, - config: { - tool: 'test-tool', - params: {}, // Missing required 'input' parameter - }, - inputs: {}, - outputs: { - output: { - response: { - text: 'string', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [], - } - - const executor = new Executor(workflow) - const result = await executor.execute('workflow-1') - - expect(result.success).toBe(false) - expect(result.error).toContain('Missing required parameter') - }) - - it('should handle tool execution errors', async () => { - const mockTool = createMockTool('test-tool', 'Test Tool', {}, 'API Error') - ;(tools as any)['test-tool'] = mockTool - - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'block-1', - position: { x: 0, y: 0 }, - config: { - tool: 'test-tool', - params: { input: 'test' }, - }, - inputs: { input: 'string' }, - outputs: { - output: { - response: { - text: 'string', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [], - } - - // Mock fetch to fail - global.fetch = jest.fn().mockImplementation(() => - Promise.resolve({ - ok: false, - json: () => Promise.resolve({ error: 'API Error' }), - }) - ) - - const executor = new Executor(workflow) - const result = await executor.execute('workflow-1') - - expect(result.success).toBe(false) - expect(result.error).toContain('API Error') - }) - }) - - describe('Interface Validation', () => { - it('should validate input types', async () => { - const mockTool = createMockTool( - 'test-tool', - 'Test Tool', - { result: 123, data: { status: 200 } }, - 'Invalid type for input' - ) - ;(tools as any)['test-tool'] = mockTool - - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'block-1', - position: { x: 0, y: 0 }, - config: { - tool: 'test-tool', - params: { input: 42 }, // Wrong type for input - }, - inputs: { input: 'string' }, - outputs: { - output: { - response: { - text: 'number', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [], - } - - const executor = new Executor(workflow) - const result = await executor.execute('workflow-1') - - expect(result.success).toBe(false) - expect(result.error).toContain('Invalid type for input') - }) - - it('should validate tool output against interface', async () => { - const mockTool = createMockTool( - 'test-tool', - 'Test Tool', - { wrongField: 'wrong type' }, - 'Tool output missing required field' - ) - ;(tools as any)['test-tool'] = mockTool - - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'block-1', - position: { x: 0, y: 0 }, - config: { - tool: 'test-tool', - params: { input: 'test' }, - }, - inputs: { input: 'string' }, - outputs: { - output: { - response: { - text: 'string', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [], - } - - // Mock fetch to return invalid output - global.fetch = jest.fn().mockImplementation(() => - Promise.resolve({ - ok: false, - json: () => Promise.resolve({ wrongField: 'wrong type' }), - }) - ) - - const executor = new Executor(workflow) - const result = await executor.execute('workflow-1') - - expect(result.success).toBe(false) - expect(result.error).toContain('Tool output missing required field') - }) - }) - - describe('Complex Workflows', () => { - it('should execute blocks in correct order and pass data between them', async () => { - const mockTool1 = createMockTool('test-tool-1', 'Test Tool 1', { - result: 'test data', - data: { status: 200 }, - }) - const mockTool2 = createMockTool('test-tool-2', 'Test Tool 2', { - result: 'processed data', - data: { status: 201 }, - }) - ;(tools as any)['test-tool-1'] = mockTool1 - ;(tools as any)['test-tool-2'] = mockTool2 - - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'block1', - position: { x: 0, y: 0 }, - config: { - tool: 'test-tool-1', - params: { input: 'initial' }, - }, - inputs: {}, - outputs: { - output: { - response: { - text: 'string', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - { - id: 'block2', - position: { x: 200, y: 0 }, - config: { - tool: 'test-tool-2', - params: { input: 'test data' }, - }, - inputs: { input: 'string' }, - outputs: { - output: { - response: { - text: 'string', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [ - { - source: 'block1', - target: 'block2', - sourceHandle: 'output.response.text', - targetHandle: 'input', - }, - ], - } - - // Mock fetch for both tools - global.fetch = jest - .fn() - .mockImplementationOnce(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ - success: true, - output: { - text: 'test data', - status: 200, - }, - }), - }) - ) - .mockImplementationOnce(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ - success: true, - output: { - text: 'processed data', - status: 201, - }, - }), - }) - ) - - const executor = new Executor(workflow) - const result = await executor.execute('workflow-1') - - expect(result.success).toBe(true) - expect(result.output).toEqual({ - response: { - text: 'processed data', - status: 201, - }, - }) - expect(global.fetch).toHaveBeenCalledTimes(2) - }) - - it('should handle cycles in workflow', async () => { - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'block-1', - position: { x: 0, y: 0 }, - config: { - tool: 'test-tool', - params: {}, - }, - inputs: {}, - outputs: { - output: { - response: { - text: 'string', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - { - id: 'block-2', - position: { x: 200, y: 0 }, - config: { - tool: 'test-tool', - params: {}, - }, - inputs: {}, - outputs: { - output: { - response: { - text: 'string', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [ - { - source: 'block-1', - target: 'block-2', - sourceHandle: 'output.response.text', - targetHandle: 'input', - }, - { - source: 'block-2', - target: 'block-1', - sourceHandle: 'output.response.text', - targetHandle: 'input', - }, - ], - } - - const executor = new Executor(workflow) - const result = await executor.execute('workflow-1') - - expect(result.success).toBe(false) - expect(result.error).toContain('Workflow contains cycles') - }) - - it('should execute a chain of API tools', async () => { - // Mock the HTTP request tools - const httpTool1: Tool = { - id: 'http.request1', - name: 'HTTP Request 1', - description: 'Make HTTP requests', - version: '1.0.0', - params: { - url: { - type: 'string', - required: true, - description: 'URL to request', - }, - method: { - type: 'string', - required: true, - description: 'HTTP method', - }, - }, - request: { - url: (params) => params.url, - method: 'GET', - headers: () => ({ 'Content-Type': 'application/json' }), - body: undefined, - }, - transformResponse: async () => ({ - success: true, - output: { - url: 'https://api.example.com/data', - method: 'GET', - }, - }), - transformError: () => 'HTTP request error', - } - - const httpTool2: Tool = { - id: 'http.request2', - name: 'HTTP Request 2', - description: 'Make HTTP requests', - version: '1.0.0', - params: { - url: { - type: 'string', - required: true, - description: 'URL to request', - }, - method: { - type: 'string', - required: true, - description: 'HTTP method', - }, - }, - request: { - url: (params) => params.url, - method: 'GET', - headers: () => ({ 'Content-Type': 'application/json' }), - body: undefined, - }, - transformResponse: async () => ({ - success: true, - output: { - message: 'Success!', - status: 200, - }, - }), - transformError: () => 'HTTP request error', - } - - ;(tools as any)['http.request1'] = httpTool1 - ;(tools as any)['http.request2'] = httpTool2 - - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'api1', - position: { x: 0, y: 0 }, - config: { - tool: 'http.request1', - params: { - url: 'https://api.example.com', - method: 'GET', - }, - }, - inputs: { - url: 'string', - method: 'string', - }, - outputs: { - output: { - response: { - url: 'string', - method: 'string', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - { - id: 'api2', - position: { x: 400, y: 0 }, - config: { - tool: 'http.request2', - params: { - url: 'https://api.example.com/data', - method: 'GET', - }, - }, - inputs: { - url: 'string', - method: 'string', - }, - outputs: { - output: { - response: { - message: 'string', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [ - { - source: 'api1', - target: 'api2', - sourceHandle: 'output.response.url', - targetHandle: 'url', - }, - ], - } - - // Mock fetch responses with sequential data flow - global.fetch = jest - .fn() - .mockImplementationOnce(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ - success: true, - output: { - url: 'https://api.example.com/data', - method: 'GET', - }, - }), - }) - ) - .mockImplementationOnce(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ - success: true, - output: { - message: 'Success!', - status: 200, - }, - }), - }) - ) - - const executor = new Executor(workflow) - const result = await executor.execute('test-workflow') - - expect(result.success).toBe(true) - expect(result.output).toEqual({ - response: { - message: 'Success!', - status: 200, - }, - }) - - // Verify the execution order and data flow - const fetchCalls = (global.fetch as jest.Mock).mock.calls - expect(fetchCalls).toHaveLength(2) - }) - }) - - describe('Connection Tests', () => { - it('should execute a chain of API tools', async () => { - // Mock the HTTP request tools - const httpTool1: Tool = { - id: 'http.request1', - name: 'HTTP Request 1', - description: 'Make HTTP requests', - version: '1.0.0', - params: { - url: { - type: 'string', - required: true, - description: 'URL to request', - }, - method: { - type: 'string', - required: true, - description: 'HTTP method', - }, - }, - request: { - url: (params) => params.url, - method: 'GET', - headers: () => ({ 'Content-Type': 'application/json' }), - body: undefined, - }, - transformResponse: async () => ({ - success: true, - output: { - url: 'https://api.example.com/data', - method: 'GET', - }, - }), - transformError: () => 'HTTP request error', - } - - const httpTool2: Tool = { - id: 'http.request2', - name: 'HTTP Request 2', - description: 'Make HTTP requests', - version: '1.0.0', - params: { - url: { - type: 'string', - required: true, - description: 'URL to request', - }, - method: { - type: 'string', - required: true, - description: 'HTTP method', - }, - }, - request: { - url: (params) => params.url, - method: 'GET', - headers: () => ({ 'Content-Type': 'application/json' }), - body: undefined, - }, - transformResponse: async () => ({ - success: true, - output: { - message: 'Success!', - status: 200, - }, - }), - transformError: () => 'HTTP request error', - } - - ;(tools as any)['http.request1'] = httpTool1 - ;(tools as any)['http.request2'] = httpTool2 - - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'api1', - position: { x: 0, y: 0 }, - config: { - tool: 'http.request1', - params: { - url: 'https://api.example.com', - method: 'GET', - }, - }, - inputs: { - url: 'string', - method: 'string', - }, - outputs: { - output: { - response: { - url: 'string', - method: 'string', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - { - id: 'api2', - position: { x: 400, y: 0 }, - config: { - tool: 'http.request2', - params: { - url: 'https://api.example.com/data', - method: 'GET', - }, - }, - inputs: { - url: 'string', - method: 'string', - }, - outputs: { - output: { - response: { - message: 'string', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [ - { - source: 'api1', - target: 'api2', - sourceHandle: 'output.response.url', - targetHandle: 'url', - }, - ], - } - - // Mock fetch responses with sequential data flow - global.fetch = jest - .fn() - .mockImplementationOnce(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ - success: true, - output: { - url: 'https://api.example.com/data', - method: 'GET', - }, - }), - }) - ) - .mockImplementationOnce(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ - success: true, - output: { - message: 'Success!', - status: 200, - }, - }), - }) - ) - - const executor = new Executor(workflow) - const result = await executor.execute('test-workflow') - - expect(result.success).toBe(true) - expect(result.output).toEqual({ - response: { - message: 'Success!', - status: 200, - }, - }) - - // Verify the execution order and data flow - const fetchCalls = (global.fetch as jest.Mock).mock.calls - expect(fetchCalls).toHaveLength(2) - }) - }) - - describe('Environment Variables', () => { - beforeEach(() => { - // Reset fetch mock before each test - global.fetch = jest.fn() - }) - - it('should resolve environment variables with double curly braces', async () => { - const mockTool = createMockTool('test-tool', 'Test Tool', { - result: 'test processed', - data: { status: 200 }, - }) - ;(tools as any)['test-tool'] = mockTool - - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'block-1', - position: { x: 0, y: 0 }, - config: { - tool: 'test-tool', - params: { input: 'test {{ENV_VAR}}' }, - }, - inputs: { input: 'string' }, - outputs: { - output: { - response: { - text: 'string', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [], - } - - // Mock fetch response - global.fetch = jest.fn().mockImplementation(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ - success: true, - output: { - text: 'test processed', - status: 200, - }, - }), - }) - ) - - const envVars = { ENV_VAR: 'value' } - const executor = new Executor(workflow, {}, envVars) - const result = await executor.execute('workflow-1') - - expect(result.success).toBe(true) - expect(global.fetch).toHaveBeenCalledWith( - 'https://api.test.com/endpoint', - expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ input: 'test value' }), - }) - ) - }) - - it('should throw error for undefined environment variables', async () => { - const mockTool = createMockTool('test-tool', 'Test Tool', { - result: 'test processed', - data: { status: 200 }, - }) - ;(tools as any)['test-tool'] = mockTool - - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'block-1', - position: { x: 0, y: 0 }, - config: { - tool: 'test-tool', - params: { input: 'test {{UNDEFINED_VAR}}' }, - }, - inputs: { input: 'string' }, - outputs: { - output: { - response: { - text: 'string', - status: 'number', - } as ValueType, - } as BlockOutput, - }, - enabled: true, - }, - ], - connections: [], - } - - const executor = new Executor(workflow) - const result = await executor.execute('workflow-1') - - expect(result.success).toBe(false) - expect(result.error).toContain('Environment variable "UNDEFINED_VAR" was not found') - }) - }) -}) diff --git a/serializer/__tests__/serializer.test.ts b/serializer/__tests__/serializer.test.ts deleted file mode 100644 index bc0234fd34..0000000000 --- a/serializer/__tests__/serializer.test.ts +++ /dev/null @@ -1,390 +0,0 @@ -import { Edge } from 'reactflow' -import { BlockState } from '@/stores/workflow/types' -import { getBlock } from '@/blocks' -import { BlockOutput } from '@/blocks/types' -import { Serializer } from '../index' -import { SerializedWorkflow } from '../types' - -// Mock icons -jest.mock('@/components/icons', () => ({ - AgentIcon: () => 'AgentIcon', - ApiIcon: () => 'ApiIcon', - CodeIcon: () => 'CodeIcon', -})) - -// Mock blocks -jest.mock('@/blocks', () => ({ - getBlock: jest.fn(), -})) - -describe('Serializer', () => { - let serializer: Serializer - - beforeEach(() => { - serializer = new Serializer() - ;(getBlock as jest.Mock).mockReset() - ;(getBlock as jest.Mock).mockImplementation((type: string) => { - if (type === 'agent') { - return { - tools: { - access: ['openai.chat'], - config: null, - }, - workflow: { - inputs: { - systemPrompt: { type: 'string', required: false }, - context: { type: 'string', required: false }, - apiKey: { type: 'string', required: false }, - }, - outputs: { - response: { - response: { - text: 'string', - model: 'string', - tokens: 'number', - }, - } satisfies BlockOutput, - }, - subBlocks: [ - { id: 'model', type: 'dropdown' }, - { id: 'systemPrompt', type: 'long-input' }, - { id: 'temperature', type: 'slider' }, - { id: 'responseFormat', type: 'code' }, - ], - }, - toolbar: { - title: 'Agent Block', - description: 'Use any LLM', - category: 'blocks', - bgColor: '#7F2FFF', - }, - } - } else if (type === 'api') { - return { - tools: { - access: ['http.request'], - config: null, - }, - workflow: { - inputs: { - url: { type: 'string', required: true }, - method: { type: 'string', required: true }, - }, - outputs: { - response: { - response: { - body: 'any', - status: 'number', - headers: 'json', - }, - } satisfies BlockOutput, - }, - subBlocks: [ - { id: 'url', type: 'short-input' }, - { id: 'method', type: 'dropdown' }, - ], - }, - toolbar: { - title: 'API Block', - description: 'Make HTTP requests', - category: '', - bgColor: '#00FF00', - }, - } - } - return { - tools: { - access: ['test-tool'], - config: null, - }, - workflow: { - inputs: {}, - outputs: { - response: { - response: { - text: 'string', - model: 'string', - tokens: 'number', - }, - } satisfies BlockOutput, - }, - subBlocks: [], - }, - toolbar: { - title: 'Test Block', - description: 'A test block', - category: 'test', - bgColor: '#000000', - }, - } - }) - }) - - describe('serializeWorkflow', () => { - it('should serialize a workflow with one tool', async () => { - const blocks: Record = { - 'block-1': { - id: 'block-1', - type: 'agent', - name: 'Test Agent', - position: { x: 0, y: 0 }, - enabled: true, - subBlocks: { - model: { - id: 'model', - type: 'dropdown', - value: 'gpt-4o', - }, - systemPrompt: { - id: 'systemPrompt', - type: 'long-input', - value: 'test', - }, - }, - outputs: { - response: { - response: { - text: 'string', - status: 'number', - }, - } satisfies BlockOutput, - }, - }, - } - - const workflow = serializer.serializeWorkflow(blocks, []) - const block = workflow.blocks[0] - - expect(block.config.tool).toBe('openai.chat') - expect(block.config.params).toEqual({ - model: 'gpt-4o', - systemPrompt: 'test', - }) - expect(block.inputs).toEqual({ - systemPrompt: 'string', - context: 'string', - apiKey: 'string', - }) - expect(block.outputs).toEqual({ - response: { - response: { - text: 'string', - status: 'number', - }, - } satisfies BlockOutput, - }) - }) - - it('should handle blocks with minimal configuration', () => { - const blocks: Record = { - 'minimal-1': { - id: 'minimal-1', - type: 'agent', - name: 'Minimal Agent', - position: { x: 0, y: 0 }, - enabled: true, - subBlocks: { - model: { - id: 'model', - type: 'dropdown', - value: 'gpt-4o', - }, - }, - outputs: { - response: { - response: { - text: 'string', - }, - } satisfies BlockOutput, - }, - }, - } - - const workflow = serializer.serializeWorkflow(blocks, []) - const block = workflow.blocks[0] - - expect(block.config.tool).toBe('openai.chat') - expect(block.config.params).toEqual({ model: 'gpt-4o' }) - expect(block.inputs).toEqual({ - systemPrompt: 'string', - context: 'string', - apiKey: 'string', - }) - expect(block.outputs).toEqual({ - response: { - response: { - text: 'string', - }, - } satisfies BlockOutput, - }) - }) - - it('should handle complex workflow with multiple blocks', () => { - const blocks: Record = { - 'input-1': { - id: 'input-1', - type: 'api', - name: 'Data Input', - position: { x: 100, y: 100 }, - enabled: true, - subBlocks: { - url: { - id: 'url', - type: 'short-input', - value: 'https://api.data.com', - }, - method: { - id: 'method', - type: 'dropdown', - value: 'GET', - }, - }, - outputs: { - response: { - response: { - body: 'json', - status: 'number', - headers: 'json', - }, - } satisfies BlockOutput, - }, - }, - 'process-1': { - id: 'process-1', - type: 'agent', - name: 'Data Processor', - position: { x: 300, y: 100 }, - enabled: true, - subBlocks: { - model: { - id: 'model', - type: 'dropdown', - value: 'gpt-4o', - }, - systemPrompt: { - id: 'systemPrompt', - type: 'long-input', - value: 'Process this data', - }, - }, - outputs: { - response: { - response: { - text: 'string', - model: 'string', - tokens: 'number', - }, - } satisfies BlockOutput, - }, - }, - } - - const connections: Edge[] = [ - { - id: 'conn-1', - source: 'input-1', - target: 'process-1', - sourceHandle: 'response', - targetHandle: 'context', - }, - ] - - const workflow = serializer.serializeWorkflow(blocks, connections) - - // Verify workflow structure - expect(workflow.blocks).toHaveLength(2) - expect(workflow.connections).toHaveLength(1) - - // Verify data flow chain - const conn = workflow.connections[0] - expect(conn.source).toBe('input-1') - expect(conn.target).toBe('process-1') - expect(conn.sourceHandle).toBe('response') - expect(conn.targetHandle).toBe('context') - - // Verify block outputs - const inputBlock = workflow.blocks.find((b) => b.id === 'input-1') - const processBlock = workflow.blocks.find((b) => b.id === 'process-1') - - expect(inputBlock?.outputs).toEqual({ - response: { - response: { - body: 'json', - status: 'number', - headers: 'json', - }, - } satisfies BlockOutput, - }) - expect(processBlock?.outputs).toEqual({ - response: { - response: { - text: 'string', - model: 'string', - tokens: 'number', - }, - } satisfies BlockOutput, - }) - }) - }) - - describe('deserializeWorkflow', () => { - it('should deserialize a workflow back to blocks and connections', () => { - const workflow: SerializedWorkflow = { - version: '1.0', - blocks: [ - { - id: 'agent-1', - position: { x: 0, y: 0 }, - config: { - tool: 'openai.chat', - params: { - model: 'gpt-4o', - systemPrompt: 'You are helpful', - }, - }, - inputs: { - systemPrompt: 'string', - context: 'string', - apiKey: 'string', - }, - outputs: { - response: { - response: { - text: 'string', - model: 'string', - tokens: 'number', - }, - } satisfies BlockOutput, - }, - metadata: { - title: 'Agent Block', - description: 'Use any LLM', - category: 'blocks', - color: '#7F2FFF', - type: 'agent', - }, - enabled: true, - }, - ], - connections: [], - } - - const { blocks } = serializer.deserializeWorkflow(workflow) - const block = blocks['agent-1'] - - expect(block.type).toBe('agent') - expect(block.enabled).toBe(true) - expect(block.subBlocks.model.value).toBe('gpt-4o') - expect(block.subBlocks.systemPrompt.value).toBe('You are helpful') - expect(block.outputs).toEqual({ - response: { - response: { - text: 'string', - model: 'string', - tokens: 'number', - }, - } satisfies BlockOutput, - }) - }) - }) -})