Skip to content

Commit a1a189f

Browse files
authored
fix(condition): remove dead code from condition handler, defer resolution to function execute tool like the function block (#2496)
1 parent 7dc4851 commit a1a189f

File tree

2 files changed

+85
-201
lines changed

2 files changed

+85
-201
lines changed

apps/sim/executor/handlers/condition/condition-handler.test.ts

Lines changed: 71 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,18 @@ vi.mock('@/tools', () => ({
2121
executeTool: vi.fn(),
2222
}))
2323

24+
vi.mock('@/executor/utils/block-data', () => ({
25+
collectBlockData: vi.fn(() => ({
26+
blockData: { 'source-block-1': { value: 10, text: 'hello' } },
27+
blockNameMapping: { 'Source Block': 'source-block-1' },
28+
})),
29+
}))
30+
31+
import { collectBlockData } from '@/executor/utils/block-data'
2432
import { executeTool } from '@/tools'
2533

2634
const mockExecuteTool = executeTool as ReturnType<typeof vi.fn>
35+
const mockCollectBlockData = collectBlockData as ReturnType<typeof vi.fn>
2736

2837
/**
2938
* Simulates what the function_execute tool does when evaluating condition code
@@ -55,8 +64,6 @@ describe('ConditionBlockHandler', () => {
5564
let mockSourceBlock: SerializedBlock
5665
let mockTargetBlock1: SerializedBlock
5766
let mockTargetBlock2: SerializedBlock
58-
let mockResolver: any
59-
let mockPathTracker: any
6067

6168
beforeEach(() => {
6269
mockSourceBlock = {
@@ -113,18 +120,11 @@ describe('ConditionBlockHandler', () => {
113120
],
114121
}
115122

116-
mockResolver = {
117-
resolveVariableReferences: vi.fn((expr) => expr),
118-
resolveBlockReferences: vi.fn((expr) => expr),
119-
resolveEnvVariables: vi.fn((expr) => expr),
120-
}
121-
122-
mockPathTracker = {}
123-
124-
handler = new ConditionBlockHandler(mockPathTracker, mockResolver)
123+
handler = new ConditionBlockHandler()
125124

126125
mockContext = {
127126
workflowId: 'test-workflow-id',
127+
workspaceId: 'test-workspace-id',
128128
blockStates: new Map<string, BlockState>([
129129
[
130130
mockSourceBlock.id,
@@ -137,7 +137,8 @@ describe('ConditionBlockHandler', () => {
137137
]),
138138
blockLogs: [],
139139
metadata: { duration: 0 },
140-
environmentVariables: {},
140+
environmentVariables: { API_KEY: 'test-key' },
141+
workflowVariables: { userName: { name: 'userName', value: 'john', type: 'plain' } },
141142
decisions: { router: new Map(), condition: new Map() },
142143
loopExecutions: new Map(),
143144
executedBlocks: new Set([mockSourceBlock.id]),
@@ -178,26 +179,41 @@ describe('ConditionBlockHandler', () => {
178179
selectedOption: 'cond1',
179180
}
180181

181-
mockResolver.resolveVariableReferences.mockReturnValue('context.value > 5')
182-
mockResolver.resolveBlockReferences.mockReturnValue('context.value > 5')
183-
mockResolver.resolveEnvVariables.mockReturnValue('context.value > 5')
184-
185182
const result = await handler.execute(mockContext, mockBlock, inputs)
186183

187-
expect(mockResolver.resolveVariableReferences).toHaveBeenCalledWith(
188-
'context.value > 5',
189-
mockBlock
190-
)
191-
expect(mockResolver.resolveBlockReferences).toHaveBeenCalledWith(
192-
'context.value > 5',
193-
mockContext,
194-
mockBlock
195-
)
196-
expect(mockResolver.resolveEnvVariables).toHaveBeenCalledWith('context.value > 5')
197184
expect(result).toEqual(expectedOutput)
198185
expect(mockContext.decisions.condition.get(mockBlock.id)).toBe('cond1')
199186
})
200187

188+
it('should pass correct parameters to function_execute tool', async () => {
189+
const conditions = [
190+
{ id: 'cond1', title: 'if', value: 'context.value > 5' },
191+
{ id: 'else1', title: 'else', value: '' },
192+
]
193+
const inputs = { conditions: JSON.stringify(conditions) }
194+
195+
await handler.execute(mockContext, mockBlock, inputs)
196+
197+
expect(mockExecuteTool).toHaveBeenCalledWith(
198+
'function_execute',
199+
expect.objectContaining({
200+
code: expect.stringContaining('context.value > 5'),
201+
timeout: 5000,
202+
envVars: mockContext.environmentVariables,
203+
workflowVariables: mockContext.workflowVariables,
204+
blockData: { 'source-block-1': { value: 10, text: 'hello' } },
205+
blockNameMapping: { 'Source Block': 'source-block-1' },
206+
_context: {
207+
workflowId: 'test-workflow-id',
208+
workspaceId: 'test-workspace-id',
209+
},
210+
}),
211+
false,
212+
false,
213+
mockContext
214+
)
215+
})
216+
201217
it('should select the else path if other conditions fail', async () => {
202218
const conditions = [
203219
{ id: 'cond1', title: 'if', value: 'context.value < 0' }, // Should fail (10 < 0 is false)
@@ -217,22 +233,8 @@ describe('ConditionBlockHandler', () => {
217233
selectedOption: 'else1',
218234
}
219235

220-
mockResolver.resolveVariableReferences.mockReturnValue('context.value < 0')
221-
mockResolver.resolveBlockReferences.mockReturnValue('context.value < 0')
222-
mockResolver.resolveEnvVariables.mockReturnValue('context.value < 0')
223-
224236
const result = await handler.execute(mockContext, mockBlock, inputs)
225237

226-
expect(mockResolver.resolveVariableReferences).toHaveBeenCalledWith(
227-
'context.value < 0',
228-
mockBlock
229-
)
230-
expect(mockResolver.resolveBlockReferences).toHaveBeenCalledWith(
231-
'context.value < 0',
232-
mockContext,
233-
mockBlock
234-
)
235-
expect(mockResolver.resolveEnvVariables).toHaveBeenCalledWith('context.value < 0')
236238
expect(result).toEqual(expectedOutput)
237239
expect(mockContext.decisions.condition.get(mockBlock.id)).toBe('else1')
238240
})
@@ -245,114 +247,13 @@ describe('ConditionBlockHandler', () => {
245247
)
246248
})
247249

248-
it('should resolve references in conditions before evaluation', async () => {
249-
const conditions = [
250-
{ id: 'cond1', title: 'if', value: '{{source-block-1.value}} > 5' },
251-
{ id: 'else1', title: 'else', value: '' },
252-
]
253-
const inputs = { conditions: JSON.stringify(conditions) }
254-
255-
mockResolver.resolveVariableReferences.mockReturnValue('{{source-block-1.value}} > 5')
256-
mockResolver.resolveBlockReferences.mockReturnValue('10 > 5')
257-
mockResolver.resolveEnvVariables.mockReturnValue('10 > 5')
258-
259-
await handler.execute(mockContext, mockBlock, inputs)
260-
261-
expect(mockResolver.resolveVariableReferences).toHaveBeenCalledWith(
262-
'{{source-block-1.value}} > 5',
263-
mockBlock
264-
)
265-
expect(mockResolver.resolveBlockReferences).toHaveBeenCalledWith(
266-
'{{source-block-1.value}} > 5',
267-
mockContext,
268-
mockBlock
269-
)
270-
expect(mockResolver.resolveEnvVariables).toHaveBeenCalledWith('10 > 5')
271-
expect(mockContext.decisions.condition.get(mockBlock.id)).toBe('cond1')
272-
})
273-
274-
it('should resolve variable references in conditions', async () => {
275-
const conditions = [
276-
{ id: 'cond1', title: 'if', value: '<variable.userName> !== null' },
277-
{ id: 'else1', title: 'else', value: '' },
278-
]
279-
const inputs = { conditions: JSON.stringify(conditions) }
280-
281-
mockResolver.resolveVariableReferences.mockReturnValue('"john" !== null')
282-
mockResolver.resolveBlockReferences.mockReturnValue('"john" !== null')
283-
mockResolver.resolveEnvVariables.mockReturnValue('"john" !== null')
284-
285-
await handler.execute(mockContext, mockBlock, inputs)
286-
287-
expect(mockResolver.resolveVariableReferences).toHaveBeenCalledWith(
288-
'<variable.userName> !== null',
289-
mockBlock
290-
)
291-
expect(mockResolver.resolveBlockReferences).toHaveBeenCalledWith(
292-
'"john" !== null',
293-
mockContext,
294-
mockBlock
295-
)
296-
expect(mockResolver.resolveEnvVariables).toHaveBeenCalledWith('"john" !== null')
297-
expect(mockContext.decisions.condition.get(mockBlock.id)).toBe('cond1')
298-
})
299-
300-
it('should resolve environment variables in conditions', async () => {
301-
const conditions = [
302-
{ id: 'cond1', title: 'if', value: '{{POOP}} === "hi"' },
303-
{ id: 'else1', title: 'else', value: '' },
304-
]
305-
const inputs = { conditions: JSON.stringify(conditions) }
306-
307-
mockResolver.resolveVariableReferences.mockReturnValue('{{POOP}} === "hi"')
308-
mockResolver.resolveBlockReferences.mockReturnValue('{{POOP}} === "hi"')
309-
mockResolver.resolveEnvVariables.mockReturnValue('"hi" === "hi"')
310-
311-
await handler.execute(mockContext, mockBlock, inputs)
312-
313-
expect(mockResolver.resolveVariableReferences).toHaveBeenCalledWith(
314-
'{{POOP}} === "hi"',
315-
mockBlock
316-
)
317-
expect(mockResolver.resolveBlockReferences).toHaveBeenCalledWith(
318-
'{{POOP}} === "hi"',
319-
mockContext,
320-
mockBlock
321-
)
322-
expect(mockResolver.resolveEnvVariables).toHaveBeenCalledWith('{{POOP}} === "hi"')
323-
expect(mockContext.decisions.condition.get(mockBlock.id)).toBe('cond1')
324-
})
325-
326-
it('should throw error if reference resolution fails', async () => {
327-
const conditions = [
328-
{ id: 'cond1', title: 'if', value: '{{invalid-ref}}' },
329-
{ id: 'else1', title: 'else', value: '' },
330-
]
331-
const inputs = { conditions: JSON.stringify(conditions) }
332-
333-
const resolutionError = new Error('Could not resolve reference: invalid-ref')
334-
mockResolver.resolveVariableReferences.mockImplementation(() => {
335-
throw resolutionError
336-
})
337-
338-
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
339-
'Failed to resolve references in condition: Could not resolve reference: invalid-ref'
340-
)
341-
})
342-
343250
it('should handle evaluation errors gracefully', async () => {
344251
const conditions = [
345252
{ id: 'cond1', title: 'if', value: 'context.nonExistentProperty.doSomething()' },
346253
{ id: 'else1', title: 'else', value: '' },
347254
]
348255
const inputs = { conditions: JSON.stringify(conditions) }
349256

350-
mockResolver.resolveVariableReferences.mockReturnValue(
351-
'context.nonExistentProperty.doSomething()'
352-
)
353-
mockResolver.resolveBlockReferences.mockReturnValue('context.nonExistentProperty.doSomething()')
354-
mockResolver.resolveEnvVariables.mockReturnValue('context.nonExistentProperty.doSomething()')
355-
356257
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
357258
/Evaluation error in condition "if".*doSomething/
358259
)
@@ -367,10 +268,6 @@ describe('ConditionBlockHandler', () => {
367268
blockStates: new Map<string, BlockState>(),
368269
}
369270

370-
mockResolver.resolveVariableReferences.mockReturnValue('true')
371-
mockResolver.resolveBlockReferences.mockReturnValue('true')
372-
mockResolver.resolveEnvVariables.mockReturnValue('true')
373-
374271
const result = await handler.execute(contextWithoutSource, mockBlock, inputs)
375272

376273
expect(result).toHaveProperty('conditionResult', true)
@@ -383,10 +280,6 @@ describe('ConditionBlockHandler', () => {
383280

384281
mockContext.workflow!.blocks = [mockSourceBlock, mockBlock, mockTargetBlock2]
385282

386-
mockResolver.resolveVariableReferences.mockReturnValue('true')
387-
mockResolver.resolveBlockReferences.mockReturnValue('true')
388-
mockResolver.resolveEnvVariables.mockReturnValue('true')
389-
390283
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
391284
`Target block ${mockTargetBlock1.id} not found`
392285
)
@@ -408,16 +301,6 @@ describe('ConditionBlockHandler', () => {
408301
},
409302
]
410303

411-
mockResolver.resolveVariableReferences
412-
.mockReturnValueOnce('false')
413-
.mockReturnValueOnce('context.value === 99')
414-
mockResolver.resolveBlockReferences
415-
.mockReturnValueOnce('false')
416-
.mockReturnValueOnce('context.value === 99')
417-
mockResolver.resolveEnvVariables
418-
.mockReturnValueOnce('false')
419-
.mockReturnValueOnce('context.value === 99')
420-
421304
const result = await handler.execute(mockContext, mockBlock, inputs)
422305

423306
expect((result as any).conditionResult).toBe(false)
@@ -433,13 +316,38 @@ describe('ConditionBlockHandler', () => {
433316
]
434317
const inputs = { conditions: JSON.stringify(conditions) }
435318

436-
mockResolver.resolveVariableReferences.mockReturnValue('context.item === "apple"')
437-
mockResolver.resolveBlockReferences.mockReturnValue('context.item === "apple"')
438-
mockResolver.resolveEnvVariables.mockReturnValue('context.item === "apple"')
439-
440319
const result = await handler.execute(mockContext, mockBlock, inputs)
441320

442321
expect(mockContext.decisions.condition.get(mockBlock.id)).toBe('else1')
443322
expect((result as any).selectedOption).toBe('else1')
444323
})
324+
325+
it('should use collectBlockData to gather block state', async () => {
326+
const conditions = [
327+
{ id: 'cond1', title: 'if', value: 'true' },
328+
{ id: 'else1', title: 'else', value: '' },
329+
]
330+
const inputs = { conditions: JSON.stringify(conditions) }
331+
332+
await handler.execute(mockContext, mockBlock, inputs)
333+
334+
expect(mockCollectBlockData).toHaveBeenCalledWith(mockContext)
335+
})
336+
337+
it('should handle function_execute tool failure', async () => {
338+
const conditions = [
339+
{ id: 'cond1', title: 'if', value: 'context.value > 5' },
340+
{ id: 'else1', title: 'else', value: '' },
341+
]
342+
const inputs = { conditions: JSON.stringify(conditions) }
343+
344+
mockExecuteTool.mockResolvedValueOnce({
345+
success: false,
346+
error: 'Execution timeout',
347+
})
348+
349+
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
350+
/Evaluation error in condition "if".*Execution timeout/
351+
)
352+
})
445353
})

0 commit comments

Comments
 (0)