Skip to content

Commit e4141e9

Browse files
waleedlatif1Vikhyath Mondreti
authored andcommitted
feat(function): added more granular error logs for function execution for easier debugging (#593)
* added more granular error logs for function execution * added tests * fixed syntax error reporting
1 parent 86168f1 commit e4141e9

4 files changed

Lines changed: 696 additions & 26 deletions

File tree

apps/sim/app/api/function/execute/route.test.ts

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,225 @@ describe('Function Execute API Route', () => {
391391
})
392392
})
393393

394+
describe('Enhanced Error Handling', () => {
395+
it('should provide detailed syntax error with line content', async () => {
396+
// Mock VM Script to throw a syntax error
397+
const mockScript = vi.fn().mockImplementation(() => {
398+
const error = new Error('Invalid or unexpected token')
399+
error.name = 'SyntaxError'
400+
error.stack = `user-function.js:5
401+
description: "This has a missing closing quote
402+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
403+
404+
SyntaxError: Invalid or unexpected token
405+
at new Script (node:vm:117:7)
406+
at POST (/path/to/route.ts:123:24)`
407+
throw error
408+
})
409+
410+
vi.doMock('vm', () => ({
411+
createContext: mockCreateContext,
412+
Script: mockScript,
413+
}))
414+
415+
const req = createMockRequest('POST', {
416+
code: 'const obj = {\n name: "test",\n description: "This has a missing closing quote\n};\nreturn obj;',
417+
timeout: 5000,
418+
})
419+
420+
const { POST } = await import('./route')
421+
const response = await POST(req)
422+
const data = await response.json()
423+
424+
expect(response.status).toBe(500)
425+
expect(data.success).toBe(false)
426+
expect(data.error).toContain('Syntax Error')
427+
expect(data.error).toContain('Line 3')
428+
expect(data.error).toContain('description: "This has a missing closing quote')
429+
expect(data.error).toContain('Invalid or unexpected token')
430+
expect(data.error).toContain('(Check for missing quotes, brackets, or semicolons)')
431+
432+
// Check debug information
433+
expect(data.debug).toBeDefined()
434+
expect(data.debug.line).toBe(3)
435+
expect(data.debug.errorType).toBe('SyntaxError')
436+
expect(data.debug.lineContent).toBe('description: "This has a missing closing quote')
437+
})
438+
439+
it('should provide detailed runtime error with line and column', async () => {
440+
// Create the error object first
441+
const runtimeError = new Error("Cannot read properties of null (reading 'someMethod')")
442+
runtimeError.name = 'TypeError'
443+
runtimeError.stack = `TypeError: Cannot read properties of null (reading 'someMethod')
444+
at user-function.js:4:16
445+
at user-function.js:9:3
446+
at Script.runInContext (node:vm:147:14)`
447+
448+
// Mock successful script creation but runtime error
449+
const mockScript = vi.fn().mockImplementation(() => ({
450+
runInContext: vi.fn().mockRejectedValue(runtimeError),
451+
}))
452+
453+
vi.doMock('vm', () => ({
454+
createContext: mockCreateContext,
455+
Script: mockScript,
456+
}))
457+
458+
const req = createMockRequest('POST', {
459+
code: 'const obj = null;\nreturn obj.someMethod();',
460+
timeout: 5000,
461+
})
462+
463+
const { POST } = await import('./route')
464+
const response = await POST(req)
465+
const data = await response.json()
466+
467+
expect(response.status).toBe(500)
468+
expect(data.success).toBe(false)
469+
expect(data.error).toContain('Type Error')
470+
expect(data.error).toContain('Line 2')
471+
expect(data.error).toContain('return obj.someMethod();')
472+
expect(data.error).toContain('Cannot read properties of null')
473+
474+
// Check debug information
475+
expect(data.debug).toBeDefined()
476+
expect(data.debug.line).toBe(2)
477+
expect(data.debug.column).toBe(16)
478+
expect(data.debug.errorType).toBe('TypeError')
479+
expect(data.debug.lineContent).toBe('return obj.someMethod();')
480+
})
481+
482+
it('should handle ReferenceError with enhanced details', async () => {
483+
// Create the error object first
484+
const referenceError = new Error('undefinedVariable is not defined')
485+
referenceError.name = 'ReferenceError'
486+
referenceError.stack = `ReferenceError: undefinedVariable is not defined
487+
at user-function.js:4:8
488+
at Script.runInContext (node:vm:147:14)`
489+
490+
const mockScript = vi.fn().mockImplementation(() => ({
491+
runInContext: vi.fn().mockRejectedValue(referenceError),
492+
}))
493+
494+
vi.doMock('vm', () => ({
495+
createContext: mockCreateContext,
496+
Script: mockScript,
497+
}))
498+
499+
const req = createMockRequest('POST', {
500+
code: 'const x = 42;\nreturn undefinedVariable + x;',
501+
timeout: 5000,
502+
})
503+
504+
const { POST } = await import('./route')
505+
const response = await POST(req)
506+
const data = await response.json()
507+
508+
expect(response.status).toBe(500)
509+
expect(data.success).toBe(false)
510+
expect(data.error).toContain('Reference Error')
511+
expect(data.error).toContain('Line 2')
512+
expect(data.error).toContain('return undefinedVariable + x;')
513+
expect(data.error).toContain('undefinedVariable is not defined')
514+
})
515+
516+
it('should handle errors without line content gracefully', async () => {
517+
const mockScript = vi.fn().mockImplementation(() => {
518+
const error = new Error('Generic error without stack trace')
519+
error.name = 'Error'
520+
// No stack trace
521+
throw error
522+
})
523+
524+
vi.doMock('vm', () => ({
525+
createContext: mockCreateContext,
526+
Script: mockScript,
527+
}))
528+
529+
const req = createMockRequest('POST', {
530+
code: 'return "test";',
531+
timeout: 5000,
532+
})
533+
534+
const { POST } = await import('./route')
535+
const response = await POST(req)
536+
const data = await response.json()
537+
538+
expect(response.status).toBe(500)
539+
expect(data.success).toBe(false)
540+
expect(data.error).toBe('Generic error without stack trace')
541+
542+
// Should still have debug info, but without line details
543+
expect(data.debug).toBeDefined()
544+
expect(data.debug.errorType).toBe('Error')
545+
expect(data.debug.line).toBeUndefined()
546+
expect(data.debug.lineContent).toBeUndefined()
547+
})
548+
549+
it('should extract line numbers from different stack trace formats', async () => {
550+
const mockScript = vi.fn().mockImplementation(() => {
551+
const error = new Error('Test error')
552+
error.name = 'Error'
553+
error.stack = `Error: Test error
554+
at user-function.js:7:25
555+
at async function
556+
at Script.runInContext (node:vm:147:14)`
557+
throw error
558+
})
559+
560+
vi.doMock('vm', () => ({
561+
createContext: mockCreateContext,
562+
Script: mockScript,
563+
}))
564+
565+
const req = createMockRequest('POST', {
566+
code: 'const a = 1;\nconst b = 2;\nconst c = 3;\nconst d = 4;\nreturn a + b + c + d;',
567+
timeout: 5000,
568+
})
569+
570+
const { POST } = await import('./route')
571+
const response = await POST(req)
572+
const data = await response.json()
573+
574+
expect(response.status).toBe(500)
575+
expect(data.success).toBe(false)
576+
577+
// Line 7 in VM should map to line 5 in user code (7 - 3 + 1 = 5)
578+
expect(data.debug.line).toBe(5)
579+
expect(data.debug.column).toBe(25)
580+
expect(data.debug.lineContent).toBe('return a + b + c + d;')
581+
})
582+
583+
it('should provide helpful suggestions for common syntax errors', async () => {
584+
const mockScript = vi.fn().mockImplementation(() => {
585+
const error = new Error('Unexpected end of input')
586+
error.name = 'SyntaxError'
587+
error.stack = 'user-function.js:4\nSyntaxError: Unexpected end of input'
588+
throw error
589+
})
590+
591+
vi.doMock('vm', () => ({
592+
createContext: mockCreateContext,
593+
Script: mockScript,
594+
}))
595+
596+
const req = createMockRequest('POST', {
597+
code: 'const obj = {\n name: "test"\n// Missing closing brace',
598+
timeout: 5000,
599+
})
600+
601+
const { POST } = await import('./route')
602+
const response = await POST(req)
603+
const data = await response.json()
604+
605+
expect(response.status).toBe(500)
606+
expect(data.success).toBe(false)
607+
expect(data.error).toContain('Syntax Error')
608+
expect(data.error).toContain('Unexpected end of input')
609+
expect(data.error).toContain('(Check for missing closing brackets or braces)')
610+
})
611+
})
612+
394613
describe('Utility Functions', () => {
395614
it('should properly escape regex special characters', async () => {
396615
// This tests the escapeRegExp function indirectly

0 commit comments

Comments
 (0)