import { createOneServerlessFunction } from 'test/integration/metadata/suites/serverless-function/utils/create-one-serverless-function.util'; import { deleteServerlessFunction } from 'test/integration/metadata/suites/serverless-function/utils/delete-serverless-function.util'; import { executeServerlessFunction } from 'test/integration/metadata/suites/serverless-function/utils/execute-serverless-function.util'; import { publishServerlessFunction } from 'test/integration/metadata/suites/serverless-function/utils/publish-serverless-function.util'; import { updateServerlessFunction } from 'test/integration/metadata/suites/serverless-function/utils/update-serverless-function.util'; import { ServerlessFunctionExecutionStatus } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result.dto'; // Test function using external packages from default layer (lodash.groupby) const EXTERNAL_PACKAGES_FUNCTION_CODE = { 'src/index.ts': `import groupBy from 'lodash.groupby'; export const main = async (params: { items: Array<{ category: string; name: string }> }): Promise => { const grouped = groupBy(params.items, 'category'); return { grouped, categories: Object.keys(grouped), }; };`, }; // Test function that throws an error const ERROR_FUNCTION_CODE = { 'src/index.ts': `export const main = async (params: { shouldFail: boolean }): Promise => { if (params.shouldFail) { throw new Error('Intentional test error'); } return { success: true }; };`, }; describe('Serverless Function Execution', () => { const createdFunctionIds: string[] = []; afterAll(async () => { // Clean up all created functions for (const functionId of createdFunctionIds) { try { await deleteServerlessFunction({ input: { id: functionId }, expectToFail: false, }); } catch { // Ignore cleanup errors } } }); it('should execute the default serverless function template', async () => { // Create the function (uses default template) const { data: createData } = await createOneServerlessFunction({ input: { name: 'Test Default Function', }, expectToFail: false, }); const functionId = createData?.createOneServerlessFunction?.id; expect(functionId).toBeDefined(); createdFunctionIds.push(functionId); // Publish the function const { data: publishData } = await publishServerlessFunction({ input: { id: functionId }, expectToFail: false, }); expect(publishData?.publishServerlessFunction?.latestVersion).toBeDefined(); // Execute with the default template's expected params: { a: string, b: number } const { data: executeData } = await executeServerlessFunction({ input: { id: functionId, payload: { a: 'hello', b: 42 }, }, expectToFail: false, }); const result = executeData?.executeOneServerlessFunction; if (result?.status !== ServerlessFunctionExecutionStatus.SUCCESS) { throw new Error(JSON.stringify(result?.error, null, 2)); } expect(result?.status).toBe(ServerlessFunctionExecutionStatus.SUCCESS); expect(result?.data).toMatchObject({ message: 'Hello, input: hello and 42', }); expect(result?.duration).toBeGreaterThan(0); }); it('should execute a function with external packages (lodash.groupby)', async () => { // Create the function (uses default template initially) const { data: createData } = await createOneServerlessFunction({ input: { name: 'External Packages Test', }, expectToFail: false, }); const functionId = createData?.createOneServerlessFunction?.id; expect(functionId).toBeDefined(); createdFunctionIds.push(functionId); // Update with custom code using external packages await updateServerlessFunction({ input: { id: functionId, update: { name: 'External Packages Test', code: EXTERNAL_PACKAGES_FUNCTION_CODE, }, }, expectToFail: false, }); // Publish the function await publishServerlessFunction({ input: { id: functionId }, expectToFail: false, }); // Execute the function with items to group const { data: executeData } = await executeServerlessFunction({ input: { id: functionId, payload: { items: [ { category: 'fruit', name: 'apple' }, { category: 'vegetable', name: 'carrot' }, { category: 'fruit', name: 'banana' }, ], }, }, expectToFail: false, }); const result = executeData?.executeOneServerlessFunction; if (result?.status !== ServerlessFunctionExecutionStatus.SUCCESS) { throw new Error(JSON.stringify(result?.error, null, 2)); } expect(result?.status).toBe(ServerlessFunctionExecutionStatus.SUCCESS); const data = result?.data as unknown as { grouped: Record>; categories: string[]; }; expect(data?.grouped).toMatchObject({ fruit: [ { category: 'fruit', name: 'apple' }, { category: 'fruit', name: 'banana' }, ], vegetable: [{ category: 'vegetable', name: 'carrot' }], }); expect(data?.categories).toEqual( expect.arrayContaining(['fruit', 'vegetable']), ); }); it('should handle errors thrown by serverless functions', async () => { // Create the function const { data: createData } = await createOneServerlessFunction({ input: { name: 'Error Test Function', }, expectToFail: false, }); const functionId = createData?.createOneServerlessFunction?.id; expect(functionId).toBeDefined(); createdFunctionIds.push(functionId); // Update with error-throwing code await updateServerlessFunction({ input: { id: functionId, update: { name: 'Error Test Function', code: ERROR_FUNCTION_CODE, }, }, expectToFail: false, }); // Publish the function await publishServerlessFunction({ input: { id: functionId }, expectToFail: false, }); // Execute with shouldFail = false (should succeed) const { data: successData } = await executeServerlessFunction({ input: { id: functionId, payload: { shouldFail: false }, }, expectToFail: false, }); expect(successData?.executeOneServerlessFunction?.status).toBe( ServerlessFunctionExecutionStatus.SUCCESS, ); expect(successData?.executeOneServerlessFunction?.data).toMatchObject({ success: true, }); // Execute with shouldFail = true (should return error status) const { data: errorData } = await executeServerlessFunction({ input: { id: functionId, payload: { shouldFail: true }, }, expectToFail: false, // The GraphQL call succeeds, but the function execution fails }); const errorResult = errorData?.executeOneServerlessFunction; expect(errorResult?.status).toBe(ServerlessFunctionExecutionStatus.ERROR); expect(errorResult?.error).toMatchObject({ errorType: 'UnhandledError', errorMessage: expect.stringContaining('Intentional test error'), }); expect(errorResult?.data).toBeNull(); }); });