Files
sim/executor/__tests__/executor.test.ts

383 lines
9.8 KiB
TypeScript

import { Executor } from '../index';
import { SerializedWorkflow } from '@/serializer/types';
import { Tool } from '../types';
import { tools } from '@/tools';
// Mock tools
const createMockTool = (
id: string,
name: string,
mockResponse: any,
mockError?: string
): 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'
}
},
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
})
},
transformResponse: () => mockResponse,
transformError: () => mockError || 'Mock error'
});
jest.mock('@/tools', () => ({
tools: {}
}));
describe('Executor', () => {
beforeEach(() => {
// Reset tools mock
(tools as any) = {};
});
describe('Tool Execution', () => {
it('should execute a simple workflow with one tool', async () => {
const mockTool = createMockTool(
'test-tool',
'Test Tool',
{ result: 'test processed' }
);
(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' },
interface: {
inputs: { input: 'string' },
outputs: { result: 'string' }
}
}
}],
connections: []
};
// Mock fetch
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ result: 'test processed' })
})
);
const executor = new Executor(workflow);
const result = await executor.execute('workflow-1');
expect(result.success).toBe(true);
expect(result.data).toEqual({ result: 'test processed' });
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 validate required parameters', async () => {
const mockTool = createMockTool(
'test-tool',
'Test Tool',
{ result: 'test processed' }
);
(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
interface: {
inputs: {},
outputs: { result: 'string' }
}
}
}],
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' },
interface: {
inputs: { input: 'string' },
outputs: { result: 'string' }
}
}
}],
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 }
);
(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
interface: {
inputs: { input: 'string' },
outputs: { result: 'number' }
}
}
}],
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' }
);
(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' },
interface: {
inputs: { input: 'string' },
outputs: { result: 'string' }
}
}
}],
connections: []
};
// Mock fetch
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
ok: true,
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(
'tool-1',
'Tool 1',
{ output: 'test data' }
);
const mockTool2 = createMockTool(
'tool-2',
'Tool 2',
{ result: 'processed data' }
);
(tools as any)['tool-1'] = mockTool1;
(tools as any)['tool-2'] = mockTool2;
const workflow: SerializedWorkflow = {
version: '1.0',
blocks: [
{
id: 'block-1',
position: { x: 0, y: 0 },
config: {
tool: 'tool-1',
params: { input: 'initial' },
interface: {
inputs: {},
outputs: { output: 'string' }
}
}
},
{
id: 'block-2',
position: { x: 200, y: 0 },
config: {
tool: 'tool-2',
params: {},
interface: {
inputs: { input: 'string' },
outputs: { result: 'string' }
}
}
}
],
connections: [
{
source: 'block-1',
target: 'block-2',
sourceHandle: 'output',
targetHandle: 'input'
}
]
};
// Mock fetch for both tools
global.fetch = jest.fn()
.mockImplementationOnce(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ output: 'test data' })
})
)
.mockImplementationOnce(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ result: 'processed data' })
})
);
const executor = new Executor(workflow);
const result = await executor.execute('workflow-1');
expect(result.success).toBe(true);
expect(result.data).toEqual({ result: 'processed data' });
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: {},
interface: {
inputs: {},
outputs: {}
}
}
},
{
id: 'block-2',
position: { x: 200, y: 0 },
config: {
tool: 'test-tool',
params: {},
interface: {
inputs: {},
outputs: {}
}
}
}
],
connections: [
{
source: 'block-1',
target: 'block-2',
sourceHandle: 'output',
targetHandle: 'input'
},
{
source: 'block-2',
target: 'block-1',
sourceHandle: 'output',
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');
});
});
});