Skip to main content
Glama
test-all-phases.ts•16.4 kB
/** * Comprehensive Test Suite for All Phases * Tests Phases 1-4 without requiring live Tableau credentials */ import * as fs from 'fs'; import * as path from 'path'; // Test colors for console output const colors = { reset: '\x1b[0m', bright: '\x1b[1m', green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', cyan: '\x1b[36m', }; function log(message: string, color: string = colors.reset) { console.log(`${color}${message}${colors.reset}`); } function testHeader(phase: string) { log(`\n${'='.repeat(60)}`, colors.cyan); log(`${phase}`, colors.cyan + colors.bright); log(`${'='.repeat(60)}`, colors.cyan); } function testResult(test: string, passed: boolean) { const symbol = passed ? 'āœ…' : 'āŒ'; const color = passed ? colors.green : colors.red; log(`${symbol} ${test}`, color); return passed; } // Test counters let totalTests = 0; let passedTests = 0; function test(description: string, testFn: () => boolean) { totalTests++; const result = testFn(); if (result) passedTests++; testResult(description, result); } // ============================================================================ // PHASE 1: PROJECT SETUP TESTS // ============================================================================ testHeader('PHASE 1: PROJECT SETUP TESTS'); test('package.json exists', () => { return fs.existsSync('package.json'); }); test('tsconfig.json exists', () => { return fs.existsSync('tsconfig.json'); }); test('Dockerfile exists', () => { return fs.existsSync('Dockerfile'); }); test('src directory exists', () => { return fs.existsSync('src') && fs.statSync('src').isDirectory(); }); test('dist directory exists (build output)', () => { return fs.existsSync('dist') && fs.statSync('dist').isDirectory(); }); test('README.md exists', () => { return fs.existsSync('README.md'); }); test('env.example exists', () => { return fs.existsSync('env.example'); }); // Check package.json content test('package.json has correct dependencies', () => { try { const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); const requiredDeps = [ '@modelcontextprotocol/sdk', 'express', 'cors', 'axios', 'zod', 'dotenv', ]; return requiredDeps.every(dep => dep in pkg.dependencies); } catch { return false; } }); // ============================================================================ // PHASE 2: TABLEAU CLIENT TESTS // ============================================================================ testHeader('PHASE 2: TABLEAU CLIENT TESTS'); test('tableau-client.ts exists', () => { return fs.existsSync('src/tableau-client.ts'); }); test('tableau-client.js compiled', () => { return fs.existsSync('dist/tableau-client.js'); }); test('types.ts exists', () => { return fs.existsSync('src/types.ts'); }); test('types.js compiled', () => { return fs.existsSync('dist/types.js'); }); // Test that TableauClient can be imported test('TableauClient can be imported', () => { try { const { TableauClient } = require('./dist/tableau-client.js'); return typeof TableauClient === 'function'; } catch (error) { log(` Error: ${error}`, colors.yellow); return false; } }); // Test TableauClient has required methods test('TableauClient has all required methods', () => { try { const { TableauClient } = require('./dist/tableau-client.js'); const mockConfig = { serverUrl: 'https://test.tableau.com', siteId: 'test-site', tokenName: 'test-token', tokenValue: 'test-value', apiVersion: '3.21', }; const client = new TableauClient(mockConfig); const requiredMethods = [ 'authenticate', 'listWorkbooks', 'listViews', 'queryViewData', 'refreshExtract', 'searchContent', 'getWorkbookMetadata', 'getViewMetadata', 'getDashboardFilters', 'exportDashboardPDF', 'exportDashboardPPTX', ]; return requiredMethods.every(method => typeof client[method] === 'function'); } catch (error) { log(` Error: ${error}`, colors.yellow); return false; } }); // ============================================================================ // PHASE 3: MCP SERVER TESTS // ============================================================================ testHeader('PHASE 3: MCP SERVER TESTS'); test('server.ts exists', () => { return fs.existsSync('src/server.ts'); }); test('server.js compiled', () => { return fs.existsSync('dist/server.js'); }); test('server.js has required imports', () => { try { const serverContent = fs.readFileSync('dist/server.js', 'utf8'); const requiredImports = [ 'express', 'Server', 'SSEServerTransport', 'TableauClient', ]; return requiredImports.every(imp => serverContent.includes(imp)); } catch { return false; } }); test('server.ts has authentication middleware', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); return serverContent.includes('authenticateAPIKey') && serverContent.includes('X-API-Key'); } catch { return false; } }); test('server.ts has SSE endpoint', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); return serverContent.includes('/sse') && serverContent.includes('SSEServerTransport'); } catch { return false; } }); test('server.ts has health check endpoints', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); return serverContent.includes('/health') && serverContent.includes('/health/live') && serverContent.includes('/health/ready'); } catch { return false; } }); test('server.ts has CORS configuration', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); return serverContent.includes('cors') && serverContent.includes('corsOptions'); } catch { return false; } }); test('server.ts has logging system', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); return serverContent.includes('Logger') && serverContent.includes('log('); } catch { return false; } }); // ============================================================================ // PHASE 4: CORE TOOLS TESTS // ============================================================================ testHeader('PHASE 4: CORE TOOLS TESTS'); const expectedTools = [ 'list-workbooks', 'list-views', 'query-view', 'refresh-extract', 'search-content', 'get-metadata', ]; // Test each tool file exists expectedTools.forEach(tool => { test(`${tool}.ts exists`, () => { return fs.existsSync(`src/tools/${tool}.ts`); }); test(`${tool}.js compiled`, () => { return fs.existsSync(`dist/tools/${tool}.js`); }); }); // Test tool structure and exports test('All tools export handler functions', () => { try { const handlers = [ 'listWorkbooksHandler', 'listViewsHandler', 'queryViewHandler', 'refreshExtractHandler', 'searchContentHandler', 'getMetadataHandler', ]; return handlers.every(handler => { const toolName = handler.replace('Handler', '').replace(/([A-Z])/g, '-$1').toLowerCase().substring(1); const toolPath = `./dist/tools/${toolName}.js`; try { const tool = require(toolPath); return typeof tool[handler] === 'function'; } catch { return false; } }); } catch (error) { log(` Error: ${error}`, colors.yellow); return false; } }); test('All tools export metadata objects', () => { try { const toolMetadata = [ 'listWorkbooksTool', 'listViewsTool', 'queryViewTool', 'refreshExtractTool', 'searchContentTool', 'getMetadataTool', ]; return toolMetadata.every(toolMeta => { const toolName = toolMeta.replace('Tool', '').replace(/([A-Z])/g, '-$1').toLowerCase().substring(1); const toolPath = `./dist/tools/${toolName}.js`; try { const tool = require(toolPath); return tool[toolMeta] && typeof tool[toolMeta].name === 'string' && typeof tool[toolMeta].description === 'string'; } catch { return false; } }); } catch (error) { log(` Error: ${error}`, colors.yellow); return false; } }); test('All tools have Zod schemas', () => { try { const schemas = [ 'ListWorkbooksArgsSchema', 'ListViewsArgsSchema', 'QueryViewArgsSchema', 'RefreshExtractArgsSchema', 'SearchContentArgsSchema', 'GetMetadataArgsSchema', ]; return schemas.every(schema => { const toolName = schema.replace('ArgsSchema', '').replace(/([A-Z])/g, '-$1').toLowerCase().substring(1); const toolPath = `./dist/tools/${toolName}.js`; try { const tool = require(toolPath); return tool[schema] !== undefined; } catch { return false; } }); } catch (error) { log(` Error: ${error}`, colors.yellow); return false; } }); // Test tool registration in server test('Server imports all tool handlers', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); return [ 'listWorkbooksHandler', 'listViewsHandler', 'queryViewHandler', 'refreshExtractHandler', 'searchContentHandler', 'getMetadataHandler', ].every(handler => serverContent.includes(handler)); } catch { return false; } }); test('Server has tool registry', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); return serverContent.includes('toolRegistry') && serverContent.includes('listWorkbooksTool') && serverContent.includes('listViewsTool'); } catch { return false; } }); test('Server registers ListToolsRequestSchema handler', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); return serverContent.includes('ListToolsRequestSchema') && serverContent.includes('setRequestHandler'); } catch { return false; } }); test('Server registers CallToolRequestSchema handler', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); return serverContent.includes('CallToolRequestSchema') && serverContent.includes('setRequestHandler'); } catch { return false; } }); test('Server has createTableauClient function', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); return serverContent.includes('createTableauClient') && serverContent.includes('return new TableauClient'); } catch { return false; } }); test('Server routes all 6 tool calls', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); const toolNames = [ 'tableau_list_workbooks', 'tableau_list_views', 'tableau_query_view', 'tableau_refresh_extract', 'tableau_search_content', 'tableau_get_metadata', ]; return toolNames.every(name => serverContent.includes(name)); } catch { return false; } }); // ============================================================================ // INTEGRATION TESTS // ============================================================================ testHeader('INTEGRATION TESTS'); test('All Phase 4 tools are importable', () => { try { const tools = [ './dist/tools/list-workbooks.js', './dist/tools/list-views.js', './dist/tools/query-view.js', './dist/tools/refresh-extract.js', './dist/tools/search-content.js', './dist/tools/get-metadata.js', ]; return tools.every(toolPath => { try { require(toolPath); return true; } catch (error) { log(` Failed to import ${toolPath}: ${error}`, colors.yellow); return false; } }); } catch { return false; } }); test('TableauClient and tools have compatible types', () => { try { const { TableauClient } = require('./dist/tableau-client.js'); const mockConfig = { serverUrl: 'https://test.tableau.com', siteId: 'test-site', tokenName: 'test-token', tokenValue: 'test-value', apiVersion: '3.21', }; const client = new TableauClient(mockConfig); // Verify tools can accept TableauClient as parameter // (This tests type compatibility without making actual API calls) return typeof client.listWorkbooks === 'function' && typeof client.authenticate === 'function'; } catch (error) { log(` Error: ${error}`, colors.yellow); return false; } }); test('Tool metadata has correct MCP schema format', () => { try { const { listWorkbooksTool } = require('./dist/tools/list-workbooks.js'); return listWorkbooksTool.name === 'tableau_list_workbooks' && typeof listWorkbooksTool.description === 'string' && typeof listWorkbooksTool.inputSchema === 'object' && listWorkbooksTool.inputSchema.type === 'object'; } catch (error) { log(` Error: ${error}`, colors.yellow); return false; } }); // ============================================================================ // CODE QUALITY TESTS // ============================================================================ testHeader('CODE QUALITY TESTS'); test('No TypeScript compilation errors', () => { // Already verified by successful build return fs.existsSync('dist/server.js') && fs.existsSync('dist/tableau-client.js'); }); test('All source files have proper structure', () => { const srcFiles = [ 'src/server.ts', 'src/tableau-client.ts', 'src/types.ts', ...expectedTools.map(t => `src/tools/${t}.ts`), ]; return srcFiles.every(file => { try { const content = fs.readFileSync(file, 'utf8'); // Check for basic structure: imports, exports, comments return content.includes('import') || content.includes('export') || content.includes('/**'); } catch { return false; } }); }); test('All tools have JSDoc documentation', () => { return expectedTools.every(tool => { try { const content = fs.readFileSync(`src/tools/${tool}.ts`, 'utf8'); return content.includes('/**') && content.includes('*/'); } catch { return false; } }); }); test('Server has error handling', () => { try { const serverContent = fs.readFileSync('src/server.ts', 'utf8'); return serverContent.includes('try') && serverContent.includes('catch') && serverContent.includes('McpError'); } catch { return false; } }); test('All tools have error handling', () => { return expectedTools.every(tool => { try { const content = fs.readFileSync(`src/tools/${tool}.ts`, 'utf8'); return content.includes('try') && content.includes('catch') && content.includes('error'); } catch { return false; } }); }); // ============================================================================ // SUMMARY // ============================================================================ log(`\n${'='.repeat(60)}`, colors.cyan); log(`TEST SUMMARY`, colors.cyan + colors.bright); log(`${'='.repeat(60)}`, colors.cyan); const passRate = ((passedTests / totalTests) * 100).toFixed(1); const color = passedTests === totalTests ? colors.green : passRate >= 90 ? colors.yellow : colors.red; log(`\nTotal Tests: ${totalTests}`, colors.bright); log(`Passed: ${passedTests}`, colors.green); log(`Failed: ${totalTests - passedTests}`, colors.red); log(`Pass Rate: ${passRate}%`, color); if (passedTests === totalTests) { log(`\nšŸŽ‰ ALL TESTS PASSED! All phases are working correctly.`, colors.green + colors.bright); log(`āœ… Phase 1: Project Setup - WORKING`, colors.green); log(`āœ… Phase 2: Tableau Client - WORKING`, colors.green); log(`āœ… Phase 3: MCP Server - WORKING`, colors.green); log(`āœ… Phase 4: Core Tools - WORKING`, colors.green); } else { log(`\nāš ļø Some tests failed. Please review the results above.`, colors.yellow); } log(''); process.exit(passedTests === totalTests ? 0 : 1);

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/russelenriquez-agile/tableau-mcp-project'

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