mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-30 01:07:59 -05:00
* fix(billing): should allow restoring subscription (#1728) * fix(already-cancelled-sub): UI should allow restoring subscription * restore functionality fixed * fix * improvement(start): revert to start block * make it work with start block * fix start block persistence * cleanup triggers * debounce status checks * update docs * improvement(start): revert to start block * make it work with start block * fix start block persistence * cleanup triggers * debounce status checks * update docs * SSE v0.1 * v0.2 * v0.3 * v0.4 * v0.5 * v0.6 * broken checkpoint * Executor progress - everything preliminarily tested except while loops and triggers * Executor fixes * Fix var typing * Implement while loop execution * Loop and parallel result agg * Refactor v1 - loops work * Fix var resolution in for each loop * Fix while loop condition and variable resolution * Fix loop iteration counts * Fix loop badges * Clean logs * Fix variable references from start block * Fix condition block * Fix conditional convergence * Dont execute orphaned nodse * Code cleanup 1 and error surfacing * compile time try catch * Some fixes * Fix error throwing * Sentinels v1 * Fix multiple start and end nodes in loop * Edge restoration * Fix reachable nodes execution * Parallel subflows * Fix loop/parallel sentinel convergence * Loops and parallels orchestrator * Split executor * Variable resolution split * Dag phase * Refactor * Refactor * Refactor 3 * Lint + refactor * Lint + cleanup + refactor * Readability * Initial logs * Fix trace spans * Console pills for iters * Add input/output pills * Checkpoint * remove unused code * THIS IS THE COMMIT THAT CAN BREAK A LOT OF THINGS * ANOTHER BIG REFACTOR * Lint + fix tests * Fix webhook * Remove comment * Merge stash * Fix triggers? * Stuff * Fix error port * Lint * Consolidate state * Clean up some var resolution * Remove some var resolution logs * Fix chat * Fix chat triggers * Fix chat trigger fully * Snapshot refactor * Fix mcp and custom tools * Lint * Fix parallel default count and trace span overlay * Agent purple * Fix test * Fix test --------- Co-authored-by: Waleed <walif6@gmail.com> Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
182 lines
5.5 KiB
TypeScript
182 lines
5.5 KiB
TypeScript
import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest'
|
|
import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/execution/constants'
|
|
import { BlockType } from '@/executor/consts'
|
|
import { FunctionBlockHandler } from '@/executor/handlers/function/function-handler'
|
|
import type { ExecutionContext } from '@/executor/types'
|
|
import type { SerializedBlock } from '@/serializer/types'
|
|
import { executeTool } from '@/tools'
|
|
|
|
vi.mock('@/lib/logs/console/logger', () => ({
|
|
createLogger: vi.fn(() => ({
|
|
info: vi.fn(),
|
|
error: vi.fn(),
|
|
warn: vi.fn(),
|
|
debug: vi.fn(),
|
|
})),
|
|
}))
|
|
|
|
vi.mock('@/tools', () => ({
|
|
executeTool: vi.fn(),
|
|
}))
|
|
|
|
const mockExecuteTool = executeTool as Mock
|
|
|
|
describe('FunctionBlockHandler', () => {
|
|
let handler: FunctionBlockHandler
|
|
let mockBlock: SerializedBlock
|
|
let mockContext: ExecutionContext
|
|
|
|
beforeEach(() => {
|
|
handler = new FunctionBlockHandler()
|
|
|
|
mockBlock = {
|
|
id: 'func-block-1',
|
|
metadata: { id: BlockType.FUNCTION, name: 'Test Function' },
|
|
position: { x: 30, y: 30 },
|
|
config: { tool: BlockType.FUNCTION, params: {} },
|
|
inputs: { code: 'string', timeout: 'number' }, // Using ParamType strings
|
|
outputs: {},
|
|
enabled: true,
|
|
}
|
|
|
|
mockContext = {
|
|
workflowId: 'test-workflow-id',
|
|
blockStates: new Map(),
|
|
blockLogs: [],
|
|
metadata: { duration: 0 },
|
|
environmentVariables: {},
|
|
decisions: { router: new Map(), condition: new Map() },
|
|
loopIterations: new Map(),
|
|
loopItems: new Map(),
|
|
executedBlocks: new Set(),
|
|
activeExecutionPath: new Set(),
|
|
completedLoops: new Set(),
|
|
}
|
|
|
|
// Reset mocks using vi
|
|
vi.clearAllMocks()
|
|
|
|
// Default mock implementation for executeTool
|
|
mockExecuteTool.mockResolvedValue({ success: true, output: { result: 'Success' } })
|
|
})
|
|
|
|
it('should handle function blocks', () => {
|
|
expect(handler.canHandle(mockBlock)).toBe(true)
|
|
const nonFuncBlock: SerializedBlock = { ...mockBlock, metadata: { id: 'other' } }
|
|
expect(handler.canHandle(nonFuncBlock)).toBe(false)
|
|
})
|
|
|
|
it('should execute function block with string code', async () => {
|
|
const inputs = {
|
|
code: 'console.log("Hello"); return 1 + 1;',
|
|
timeout: 10000,
|
|
envVars: {},
|
|
isCustomTool: false,
|
|
workflowId: undefined,
|
|
}
|
|
const expectedToolParams = {
|
|
code: inputs.code,
|
|
language: 'javascript',
|
|
useLocalVM: true,
|
|
timeout: inputs.timeout,
|
|
envVars: {},
|
|
workflowVariables: {},
|
|
blockData: {},
|
|
blockNameMapping: {},
|
|
_context: { workflowId: mockContext.workflowId, workspaceId: mockContext.workspaceId },
|
|
}
|
|
const expectedOutput: any = { result: 'Success' }
|
|
|
|
const result = await handler.execute(mockContext, mockBlock, inputs)
|
|
|
|
expect(mockExecuteTool).toHaveBeenCalledWith(
|
|
'function_execute',
|
|
expectedToolParams,
|
|
false, // skipProxy
|
|
false, // skipPostProcess
|
|
mockContext // execution context
|
|
)
|
|
expect(result).toEqual(expectedOutput)
|
|
})
|
|
|
|
it('should execute function block with array code', async () => {
|
|
const inputs = {
|
|
code: [{ content: 'const x = 5;' }, { content: 'return x * 2;' }],
|
|
timeout: 5000,
|
|
envVars: {},
|
|
isCustomTool: false,
|
|
workflowId: undefined,
|
|
}
|
|
const expectedCode = 'const x = 5;\nreturn x * 2;'
|
|
const expectedToolParams = {
|
|
code: expectedCode,
|
|
language: 'javascript',
|
|
useLocalVM: true,
|
|
timeout: inputs.timeout,
|
|
envVars: {},
|
|
workflowVariables: {},
|
|
blockData: {},
|
|
blockNameMapping: {},
|
|
_context: { workflowId: mockContext.workflowId, workspaceId: mockContext.workspaceId },
|
|
}
|
|
const expectedOutput: any = { result: 'Success' }
|
|
|
|
const result = await handler.execute(mockContext, mockBlock, inputs)
|
|
|
|
expect(mockExecuteTool).toHaveBeenCalledWith(
|
|
'function_execute',
|
|
expectedToolParams,
|
|
false, // skipProxy
|
|
false, // skipPostProcess
|
|
mockContext // execution context
|
|
)
|
|
expect(result).toEqual(expectedOutput)
|
|
})
|
|
|
|
it('should use default timeout if not provided', async () => {
|
|
const inputs = { code: 'return true;' }
|
|
const expectedToolParams = {
|
|
code: inputs.code,
|
|
language: 'javascript',
|
|
useLocalVM: true,
|
|
timeout: DEFAULT_EXECUTION_TIMEOUT_MS,
|
|
envVars: {},
|
|
workflowVariables: {},
|
|
blockData: {},
|
|
blockNameMapping: {},
|
|
_context: { workflowId: mockContext.workflowId, workspaceId: mockContext.workspaceId },
|
|
}
|
|
|
|
await handler.execute(mockContext, mockBlock, inputs)
|
|
|
|
expect(mockExecuteTool).toHaveBeenCalledWith(
|
|
'function_execute',
|
|
expectedToolParams,
|
|
false, // skipProxy
|
|
false, // skipPostProcess
|
|
mockContext // execution context
|
|
)
|
|
})
|
|
|
|
it('should handle execution errors from the tool', async () => {
|
|
const inputs = { code: 'throw new Error("Code failed");' }
|
|
const errorResult = { success: false, error: 'Function execution failed: Code failed' }
|
|
mockExecuteTool.mockResolvedValue(errorResult)
|
|
|
|
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
|
|
'Function execution failed: Code failed'
|
|
)
|
|
expect(mockExecuteTool).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should handle tool error with no specific message', async () => {
|
|
const inputs = { code: 'some code' }
|
|
const errorResult = { success: false }
|
|
mockExecuteTool.mockResolvedValue(errorResult)
|
|
|
|
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
|
|
'Function execution failed'
|
|
)
|
|
})
|
|
})
|