Skip to main content
Glama

Smartsheet MCP Server

mcp-workflow.test.js19.2 kB
/** * Integration tests for MCP workflow end-to-end functionality */ import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals'; import { createMockSmartsheetServer, createMockExec, createMockCliResponse } from '../mocks/mcp-mock.js'; // Mock child_process jest.unstable_mockModule('child_process', () => ({ exec: createMockExec() })); describe('MCP Workflow Integration Tests', () => { let mockServer; let mockExec; beforeEach(async () => { mockServer = createMockSmartsheetServer({ pythonPath: '/usr/bin/python3', apiKey: 'test-api-key', enableLogging: false }); const { exec } = jest.requireMock('child_process'); mockExec = exec; 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.mockImplementationOnce((command, options, callback) => { expect(command).toContain('--operation get_column_map'); expect(command).toContain(`--sheet-id ${sheetId}`); callback(null, { stdout: expectedCliResponse, stderr: '' }); }); // 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.mockImplementationOnce((command, options, callback) => { expect(command).toContain('--operation get_sheet_cross_references'); expect(command).toContain(`--sheet-id ${sheetId}`); callback(null, { stdout: expectedCliResponse, stderr: '' }); }); 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.mockImplementationOnce((command, options, callback) => { expect(command).toContain('--operation create_discussion'); expect(command).toContain(`--sheet-id ${sheetId}`); expect(command).toContain('--data'); // Verify JSON data contains our discussion data const dataMatch = command.match(/--data\s+'([^']+)'/); if (dataMatch) { const parsedData = JSON.parse(dataMatch[1]); expect(parsedData.discussion_type).toBe(discussionData.discussion_type); expect(parsedData.comment_text).toBe(discussionData.comment_text); } callback(null, { stdout: expectedCliResponse, stderr: '' }); }); 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.mockImplementationOnce((command, options, callback) => { expect(command).toContain('--operation upload_attachment'); expect(command).toContain(`--sheet-id ${sheetId}`); callback(null, { stdout: expectedCliResponse, stderr: '' }); }); 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.mockImplementationOnce((command, options, callback) => { callback(new Error('Python script failed'), null); }); // This would test the actual server's error handling // For now, we verify the mock setup expect(mockExec).toBeDefined(); }); test('should handle Python stderr output', async () => { const errorMessage = 'API key invalid'; mockExec.mockImplementationOnce((command, options, callback) => { const errorResponse = createMockCliResponse('get_column_map', false, { error: errorMessage }); callback(null, { stdout: errorResponse, stderr: errorMessage }); }); // Verify error handling setup expect(mockExec).toBeDefined(); }); test('should handle malformed JSON responses', async () => { mockExec.mockImplementationOnce((command, options, callback) => { callback(null, { stdout: 'Invalid JSON{', stderr: '' }); }); // Test JSON parsing error handling expect(mockExec).toBeDefined(); }); test('should handle timeout errors', async () => { mockExec.mockImplementationOnce((command, options, callback) => { const error = new Error('Command timeout'); error.killed = true; error.signal = 'SIGTERM'; callback(error, null); }); // Test timeout handling expect(mockExec).toBeDefined(); }); }); 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.mockImplementationOnce((command, options, callback) => { expect(command).toContain('--operation add_rows'); expect(command).toContain(`--sheet-id ${sheetId}`); expect(command).toContain('--data'); // Verify JSON serialization const dataMatch = command.match(/--data\s+'([^']+)'/); if (dataMatch) { const parsedData = JSON.parse(dataMatch[1]); expect(parsedData.row_data).toEqual(complexData.row_data); expect(parsedData.column_map).toEqual(complexData.column_map); } const response = createMockCliResponse('add_rows', true, { rows_added: 1, row_ids: ['5555555555555555'] }); callback(null, { stdout: response, stderr: '' }); }); 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.mockImplementationOnce((command, options, callback) => { expect(command).toContain('--operation create_discussion'); // Verify special character handling const dataMatch = command.match(/--data\s+'([^']+)'/); if (dataMatch) { const parsedData = JSON.parse(dataMatch[1]); expect(parsedData.comment_text).toBe(specialData.comment_text); } const response = createMockCliResponse('create_discussion', true, { discussion_id: '1111111111111111' }); callback(null, { stdout: response, stderr: '' }); }); 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, options, callback) => { const currentSheetId = sheetIds[callCount]; const response = createMockCliResponse('get_column_map', true, { sheet_id: currentSheetId, column_map: { 'Task': '7777777777777777' } }); callCount++; // Simulate async behavior setTimeout(() => { callback(null, { stdout: response, 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, options, callback) => { const testCase = testCases[callIndex]; callIndex++; if (testCase.shouldSucceed) { const response = createMockCliResponse('get_column_map', true, { sheet_id: testCase.sheetId }); callback(null, { stdout: response, stderr: '' }); } else { const errorResponse = createMockCliResponse('get_column_map', false, { error: 'Sheet not found' }); callback(null, { stdout: errorResponse, 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.mockImplementationOnce((command, options, callback) => { // Verify timeout is set correctly expect(options.timeout).toBe(300000); // 5 minutes const response = createMockCliResponse('get_column_map', true); callback(null, { stdout: response, 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.mockImplementationOnce((command, options, callback) => { // Verify buffer size is set correctly expect(options.maxBuffer).toBe(10 * 1024 * 1024); // 10MB const response = createMockCliResponse('get_column_map', true); callback(null, { stdout: response, 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.mockImplementationOnce((command, options, callback) => { callback(null, { stdout: JSON.stringify(largeResponse), stderr: '' }); }); 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); }); }); }); //# sourceMappingURL=mcp-workflow.test.js.map

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