Files
sim/apps/sim/executor/handlers/function/function-handler.test.ts
Siddharth Ganesan 3bf00cbd2a improvement(executor): redesign executor + add start block (#1790)
* 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>
2025-11-02 12:21:16 -08:00

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'
)
})
})