Skip to main content
Glama

Smartsheet MCP Server

mcp-workflow.test.ts14.5 kB
/** * Integration tests for MCP workflow end-to-end functionality */ import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals'; import { createMockSmartsheetServerWithTools, createConfigurableMockExec, createMockCliResponse } from '../mocks/mcp-mock'; import type { MockMcpServer } from '../mocks/mcp-mock'; // Mock child_process jest.unstable_mockModule('child_process', () => ({ exec: jest.fn() })); describe('MCP Workflow Integration Tests', () => { let mockServer: MockMcpServer; let mockExec: any; beforeEach(async () => { mockServer = createMockSmartsheetServerWithTools({ pythonPath: '/usr/bin/python3', apiKey: 'test-api-key', enableLogging: false }); const { exec } = jest.requireMock('child_process') as any; mockExec = createConfigurableMockExec(); // Replace the mocked exec with our configurable version jest.mocked(exec).mockImplementation(mockExec); jest.clearAllMocks(); }); afterEach(() => { jest.restoreAllMocks(); }); describe('Complete Tool Execution Flow', () => { test('should execute get_column_map workflow end-to-end', async () => { const sheetId = '1234567890123456'; // Mock Python CLI response const expectedCliResponse = createMockCliResponse('get_column_map', true, { sheet_id: sheetId, column_map: { 'Task Name': '7777777777777777', 'Status': '8888888888888888' }, sample_data: [ { 'Task Name': 'Test Task', 'Status': 'In Progress' } ] }); mockExec.mockSuccess(JSON.parse(expectedCliResponse)); // Execute through MCP server const result = await mockServer.callTool({ method: 'tools/call', params: { name: 'get_column_map', arguments: { sheet_id: sheetId } } }); // Verify result structure expect(result.content).toBeDefined(); expect(result.content[0].type).toBe('text'); const responseData = JSON.parse(result.content[0].text); expect(responseData.success).toBe(true); expect(responseData.sheet_id).toBe(sheetId); expect(responseData.column_map).toBeDefined(); }); test('should execute cross-reference analysis workflow', async () => { const sheetId = '1234567890123456'; const expectedCliResponse = createMockCliResponse('get_sheet_cross_references', true, { sheet_id: sheetId, total_references: 2, cross_references: [ { row_id: '5555555555555555', column_id: '7777777777777777', reference: '[Target Sheet]Column1', referenced_sheet_name: 'Target Sheet' } ] }); mockExec.mockSuccess(JSON.parse(expectedCliResponse)); const result = await mockServer.callTool({ method: 'tools/call', params: { name: 'smartsheet_get_sheet_cross_references', arguments: { sheet_id: sheetId, include_details: true } } }); const responseData = JSON.parse(result.content[0].text); expect(responseData.success).toBe(true); expect(responseData.total_references).toBeGreaterThanOrEqual(0); expect(responseData.cross_references).toBeDefined(); }); test('should execute discussion creation workflow', async () => { const sheetId = '1234567890123456'; const discussionData = { discussion_type: 'sheet', comment_text: 'Test discussion', title: 'Integration Test Discussion' }; const expectedCliResponse = createMockCliResponse('create_discussion', true, { discussion_id: '1111111111111111', discussion_type: 'sheet', title: discussionData.title, comment_count: 1 }); mockExec.mockSuccess(JSON.parse(expectedCliResponse)); const result = await mockServer.callTool({ method: 'tools/call', params: { name: 'smartsheet_create_discussion', arguments: { sheet_id: sheetId, ...discussionData } } }); const responseData = JSON.parse(result.content[0].text); expect(responseData.success).toBe(true); expect(responseData.discussion_id).toBeDefined(); }); test('should execute attachment upload workflow', async () => { const sheetId = '1234567890123456'; const attachmentData = { file_path: '/tmp/test_file.pdf', attachment_type: 'sheet', file_name: 'Test Document.pdf' }; const expectedCliResponse = createMockCliResponse('upload_attachment', true, { attachment_id: '3333333333333333', file_name: attachmentData.file_name, attachment_type: 'sheet', file_size: 1024 }); mockExec.mockSuccess(JSON.parse(expectedCliResponse)); const result = await mockServer.callTool({ method: 'tools/call', params: { name: 'smartsheet_upload_attachment', arguments: { sheet_id: sheetId, ...attachmentData } } }); const responseData = JSON.parse(result.content[0].text); expect(responseData.success).toBe(true); expect(responseData.attachment_id).toBeDefined(); }); }); describe('Error Handling Integration', () => { test('should handle Python execution errors gracefully', async () => { mockExec.mockError('Python script failed'); await expect( mockServer.callTool({ method: 'tools/call', params: { name: 'get_column_map', arguments: { sheet_id: 'test-sheet' } } }) ).rejects.toThrow(); }); test('should handle Python stderr output', async () => { const errorMessage = 'API key invalid'; mockExec.mockStderrError(errorMessage); const result = await mockServer.callTool({ method: 'tools/call', params: { name: 'get_column_map', arguments: { sheet_id: 'test-sheet' } } }); const responseData = JSON.parse(result.content[0].text); expect(responseData.success).toBe(false); expect(responseData.error).toBe(errorMessage); }); test('should handle malformed JSON responses', async () => { mockExec.mockInvalidJson(); // This should return invalid JSON response const result = await mockServer.callTool({ method: 'tools/call', params: { name: 'get_column_map', arguments: { sheet_id: 'test-sheet' } } }); // Check that we got invalid JSON back expect(result.content[0].text).toBe('Invalid JSON{'); }); test('should handle timeout errors', async () => { mockExec.mockTimeout(); await expect( mockServer.callTool({ method: 'tools/call', params: { name: 'get_column_map', arguments: { sheet_id: 'test-sheet' } } }) ).rejects.toThrow(); }); }); describe('Parameter Serialization', () => { test('should correctly serialize complex data parameters', async () => { const sheetId = '1234567890123456'; const complexData = { row_data: [ { 'Task Name': 'Complex Task', 'Status': 'In Progress', 'Priority': 'High' } ], column_map: { 'Task Name': '7777777777777777', 'Status': '8888888888888888', 'Priority': '9999999999999999' } }; mockExec.mockSuccess({ success: true, operation: 'add_rows', rows_added: 1, row_ids: ['5555555555555555'] }); const result = await mockServer.callTool({ method: 'tools/call', params: { name: 'smartsheet_write', arguments: { sheet_id: sheetId, ...complexData } } }); expect(result.content).toBeDefined(); }); test('should handle special characters in data', async () => { const sheetId = '1234567890123456'; const specialData = { comment_text: 'Test with "quotes" and \'apostrophes\' and newlines\n\nand unicode: 中文', discussion_type: 'sheet' }; mockExec.mockSuccess({ success: true, operation: 'create_discussion', discussion_id: '1111111111111111' }); const result = await mockServer.callTool({ method: 'tools/call', params: { name: 'smartsheet_create_discussion', arguments: { sheet_id: sheetId, ...specialData } } }); expect(result.content).toBeDefined(); }); }); describe('Concurrent Operations', () => { test('should handle multiple concurrent tool calls', async () => { const sheetIds = ['1111111111111111', '2222222222222222', '3333333333333333']; // Mock responses for each call let callCount = 0; mockExec.mockImplementation((command: string, options: any, callback: any) => { const currentSheetId = sheetIds[callCount]; const response = { success: true, operation: 'get_column_map', sheet_id: currentSheetId, column_map: { 'Task': '7777777777777777' } }; callCount++; // Simulate async behavior setTimeout(() => { callback(null, { stdout: JSON.stringify(response, null, 2), stderr: '' }); }, Math.random() * 10); }); // Execute multiple calls concurrently const promises = sheetIds.map(sheetId => mockServer.callTool({ method: 'tools/call', params: { name: 'get_column_map', arguments: { sheet_id: sheetId } } }) ); const results = await Promise.all(promises); expect(results).toHaveLength(3); results.forEach((result, index) => { expect(result.content).toBeDefined(); const responseData = JSON.parse(result.content[0].text); expect(responseData.sheet_id).toBe(sheetIds[index]); }); }); test('should handle mixed success and failure scenarios', async () => { const testCases = [ { sheetId: '1111111111111111', shouldSucceed: true }, { sheetId: 'invalid_sheet', shouldSucceed: false }, { sheetId: '3333333333333333', shouldSucceed: true } ]; let callIndex = 0; mockExec.mockImplementation((command: string, options: any, callback: any) => { const testCase = testCases[callIndex]; callIndex++; if (testCase.shouldSucceed) { const response = { success: true, operation: 'get_column_map', sheet_id: testCase.sheetId }; callback(null, { stdout: JSON.stringify(response, null, 2), stderr: '' }); } else { const errorResponse = { success: false, operation: 'get_column_map', error: 'Sheet not found' }; callback(null, { stdout: JSON.stringify(errorResponse, null, 2), stderr: 'Sheet not found' }); } }); // Execute mixed calls const promises = testCases.map(testCase => mockServer.callTool({ method: 'tools/call', params: { name: 'get_column_map', arguments: { sheet_id: testCase.sheetId } } }) ); const results = await Promise.all(promises); results.forEach((result, index) => { const responseData = JSON.parse(result.content[0].text); if (testCases[index].shouldSucceed) { expect(responseData.success).toBe(true); } else { expect(responseData.success).toBe(false); expect(responseData.error).toBeDefined(); } }); }); }); describe('Performance and Resource Management', () => { test('should respect execution timeouts', async () => { mockExec.mockImplementation((command: string, options: any, callback: any) => { // Verify timeout is set correctly expect(options.timeout).toBe(300000); // 5 minutes const response = { success: true, operation: 'get_column_map' }; callback(null, { stdout: JSON.stringify(response, null, 2), stderr: '' }); }); await mockServer.callTool({ method: 'tools/call', params: { name: 'get_column_map', arguments: { sheet_id: '1234567890123456' } } }); expect(mockExec).toHaveBeenCalled(); }); test('should respect buffer size limits', async () => { mockExec.mockImplementation((command: string, options: any, callback: any) => { // Verify buffer size is set correctly expect(options.maxBuffer).toBe(10 * 1024 * 1024); // 10MB const response = { success: true, operation: 'get_column_map' }; callback(null, { stdout: JSON.stringify(response, null, 2), stderr: '' }); }); await mockServer.callTool({ method: 'tools/call', params: { name: 'get_column_map', arguments: { sheet_id: '1234567890123456' } } }); expect(mockExec).toHaveBeenCalled(); }); test('should handle large response payloads', async () => { const largeResponse = { success: true, operation: 'get_column_map', column_map: {}, sample_data: [] }; // Generate large sample data for (let i = 0; i < 1000; i++) { largeResponse.sample_data.push({ 'Task Name': `Large Task ${i}`, 'Description': `Very long description that repeats many times: ${'Lorem ipsum '.repeat(100)}`, 'Status': 'In Progress' }); largeResponse.column_map[`Column_${i}`] = `77777777777777${i.toString().padStart(2, '0')}`; } mockExec.mockSuccess(largeResponse); const result = await mockServer.callTool({ method: 'tools/call', params: { name: 'get_column_map', arguments: { sheet_id: '1234567890123456' } } }); expect(result.content).toBeDefined(); const responseData = JSON.parse(result.content[0].text); expect(responseData.sample_data).toHaveLength(1000); }); }); });

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/terilios/smartsheet-server'

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