Skip to main content
Glama
invokeAiAction.test.ts8.36 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { invokeAiActionTool } from './invokeAiAction.js'; import { formatResponse } from '../../utils/formatters.js'; import { OutputFormat } from '../../utils/ai-actions.js'; import { setupMockClient, mockAiActionInvoke, mockAiActionInvocationGet, mockArgs, mockInvocationFields, mockInvocationStatusResponse, } from './mockClient.js'; import { createMockConfig } from '../../test-helpers/mockConfig.js'; vi.mock('../../utils/tools.js'); describe('invokeAiAction', () => { const mockConfig = createMockConfig(); beforeEach(() => { setupMockClient(); // Use fake timers to simulate time passage vi.useFakeTimers(); }); afterEach(() => { // Clean up fake timers after each test vi.useRealTimers(); }); it('should invoke an AI action successfully and poll for completion', async () => { const testArgs = { ...mockArgs, fields: mockInvocationFields, }; const mockInvocation = { sys: { id: 'test-invocation-id', }, }; // Mock the invoke call mockAiActionInvoke.mockResolvedValue(mockInvocation); // Mock the polling status check mockAiActionInvocationGet.mockResolvedValue(mockInvocationStatusResponse); // Start the invocation and immediately advance timers const tool = invokeAiActionTool(mockConfig); const resultPromise = tool(testArgs); // Fast-forward through the polling interval (30 seconds) await vi.advanceTimersByTimeAsync(30000); const result = await resultPromise; const expectedResponse = formatResponse( 'AI action invoked and completed successfully', { aiActions: [ { actionId: 'test-invocation-id', content: 'AI action completed successfully', }, ], nextStepsGuidance: `Required next step: Now that the invocation is complete, you will take the next step to complete the workflow. This will involve a CRUD operation that will take action on each of the action returned in the aiActions variable. You will need to use the appropriate tool to do this, choose a tool depending on the type of entity the action was executed on and the description of the Ai Action.`, }, ); expect(result).toEqual({ content: [ { type: 'text', text: expectedResponse, }, ], }); }); it('should handle multiple field invocations', async () => { const multipleFields = [ { outputFormat: OutputFormat.PLAIN_TEXT, variables: [ { id: 'text1', value: 'First text', }, ], }, { outputFormat: OutputFormat.MARKDOWN, variables: [ { id: 'text2', value: 'Second text', }, ], }, ]; const testArgs = { ...mockArgs, fields: multipleFields, }; const mockInvocations = [ { sys: { id: 'invocation-1' } }, { sys: { id: 'invocation-2' } }, ]; mockAiActionInvoke .mockResolvedValueOnce(mockInvocations[0]) .mockResolvedValueOnce(mockInvocations[1]); mockAiActionInvocationGet .mockResolvedValueOnce({ sys: { id: 'invocation-1', status: 'COMPLETED' }, result: { content: 'Result 1' }, }) .mockResolvedValueOnce({ sys: { id: 'invocation-2', status: 'COMPLETED' }, result: { content: 'Result 2' }, }); // Start the invocation and advance timers const tool = invokeAiActionTool(mockConfig); const resultPromise = tool(testArgs); // Fast-forward through the polling interval await vi.advanceTimersByTimeAsync(30000); const result = await resultPromise; const expectedResponse = formatResponse( 'AI action invoked and completed successfully', { aiActions: [ { actionId: 'invocation-1', content: 'Result 1', }, { actionId: 'invocation-2', content: 'Result 2', }, ], nextStepsGuidance: `Required next step: Now that the invocation is complete, you will take the next step to complete the workflow. This will involve a CRUD operation that will take action on each of the action returned in the aiActions variable. You will need to use the appropriate tool to do this, choose a tool depending on the type of entity the action was executed on and the description of the Ai Action.`, }, ); expect(result).toEqual({ content: [ { type: 'text', text: expectedResponse, }, ], }); }); it('should handle polling timeout when invocation takes too long', async () => { const testArgs = { ...mockArgs, fields: mockInvocationFields, }; const mockInvocation = { sys: { id: 'test-invocation-id', }, }; mockAiActionInvoke.mockResolvedValue(mockInvocation); // Mock the status to always return IN_PROGRESS (never completes) mockAiActionInvocationGet.mockResolvedValue({ sys: { id: 'test-invocation-id', status: 'IN_PROGRESS', }, }); // Start the invocation const tool = invokeAiActionTool(mockConfig); const resultPromise = tool(testArgs); // Fast-forward through all 10 polling attempts await vi.advanceTimersByTimeAsync(10 * 30000); const result = await resultPromise; expect(result).toEqual({ isError: true, content: [ { type: 'text', text: 'Error invoking AI action: Polling timeout: 0/1 actions completed after 10 attempts', }, ], }); }); it('should handle failed invocation status', async () => { const testArgs = { ...mockArgs, fields: mockInvocationFields, }; const mockInvocation = { sys: { id: 'test-invocation-id', }, }; mockAiActionInvoke.mockResolvedValue(mockInvocation); // Mock the status to return FAILED mockAiActionInvocationGet.mockResolvedValue({ sys: { id: 'test-invocation-id', status: 'FAILED', }, }); // Start the invocation const tool = invokeAiActionTool(mockConfig); const resultPromise = tool(testArgs); // Fast-forward through all polling attempts since FAILED status never completes await vi.advanceTimersByTimeAsync(10 * 30000); const result = await resultPromise; expect(result).toEqual({ isError: true, content: [ { type: 'text', text: 'Error invoking AI action: Polling timeout: 0/1 actions completed after 10 attempts', }, ], }); }); it('should handle cancelled invocation status', async () => { const testArgs = { ...mockArgs, fields: mockInvocationFields, }; const mockInvocation = { sys: { id: 'test-invocation-id', }, }; mockAiActionInvoke.mockResolvedValue(mockInvocation); // Mock the status to return CANCELLED mockAiActionInvocationGet.mockResolvedValue({ sys: { id: 'test-invocation-id', status: 'CANCELLED', }, }); // Start the invocation const tool = invokeAiActionTool(mockConfig); const resultPromise = tool(testArgs); // Fast-forward through all polling attempts since CANCELLED status never completes await vi.advanceTimersByTimeAsync(10 * 30000); const result = await resultPromise; expect(result).toEqual({ isError: true, content: [ { type: 'text', text: 'Error invoking AI action: Polling timeout: 0/1 actions completed after 10 attempts', }, ], }); }); it('should handle errors during initial invocation', async () => { const testArgs = { ...mockArgs, fields: mockInvocationFields, }; const error = new Error('AI action not found'); mockAiActionInvoke.mockRejectedValue(error); const tool = invokeAiActionTool(mockConfig); const result = await tool(testArgs); expect(result).toEqual({ isError: true, content: [ { type: 'text', text: 'Error invoking AI action: AI action not found', }, ], }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/contentful/contentful-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server