Skip to main content
Glama
mcp-query-optimization-tools.test.js22.6 kB
import { describe, test, expect, beforeEach, afterEach } from 'vitest'; import { setupMssqlMock, setupStdioMock, // setupMcpTest, resetEnvironment, setupTestEnvironment, createTestMcpServerV4, mockPool, mockRequest // testData } from './mcp-shared-fixtures.js'; // Setup module mocks setupMssqlMock(); setupStdioMock(); describe('Query Optimization MCP Tools (TDD Tests)', () => { let mcpServer; // Mock test data for optimization tools const mockOptimizationData = { // Index recommendations indexRecommendations: [ { database_name: 'TestDB', table_name: 'Users', schema_name: 'dbo', recommended_index: 'IX_Users_LastName_FirstName', columns: 'LastName, FirstName', index_type: 'NONCLUSTERED', impact_score: 85.7, estimated_improvement: '45% faster queries', missing_index_handle: 1234, user_seeks: 1500, user_scans: 25, avg_total_user_cost: 12.45, avg_user_impact: 85.7, sample_queries: [ 'SELECT * FROM Users WHERE LastName = ? AND FirstName = ?', 'SELECT COUNT(*) FROM Users WHERE LastName LIKE ?' ] }, { database_name: 'TestDB', table_name: 'Orders', schema_name: 'dbo', recommended_index: 'IX_Orders_CustomerID_OrderDate', columns: 'CustomerID, OrderDate', index_type: 'NONCLUSTERED', impact_score: 72.3, estimated_improvement: '35% faster queries', missing_index_handle: 1235, user_seeks: 890, user_scans: 15, avg_total_user_cost: 8.23, avg_user_impact: 72.3, sample_queries: ['SELECT * FROM Orders WHERE CustomerID = ? AND OrderDate >= ?'] } ], // Query bottlenecks queryBottlenecks: [ { query_hash: 'ABC123DEF456', query_text: 'SELECT * FROM LargeTable WHERE Status = ? AND CreatedDate >= ?', database_name: 'TestDB', avg_duration_ms: 8500, total_executions: 450, avg_cpu_time_ms: 3200, avg_logical_reads: 25000, avg_physical_reads: 1200, avg_writes: 0, avg_wait_time_ms: 1800, wait_stats: [ { wait_type: 'PAGEIOLATCH_SH', wait_time_ms: 1200, wait_count: 180 }, { wait_type: 'LCK_M_S', wait_time_ms: 600, wait_count: 45 } ], bottleneck_type: 'IO_INTENSIVE', severity: 'HIGH', recommendations: [ 'Consider adding index on Status, CreatedDate columns', 'Review query to reduce logical reads', 'Consider query optimization or table partitioning' ] } ], // Query analysis results queryAnalysis: { query: 'SELECT u.Name, COUNT(o.OrderID) FROM Users u LEFT JOIN Orders o ON u.UserID = o.CustomerID WHERE u.CreatedDate >= ? GROUP BY u.Name', database_name: 'TestDB', analysis: { query_type: 'SELECT_WITH_JOIN_AND_AGGREGATION', complexity_score: 7.5, estimated_cost: 15.67, table_access_methods: [ { table: 'Users', access_method: 'CLUSTERED_INDEX_SCAN', estimated_rows: 10000, cost: 8.23 }, { table: 'Orders', access_method: 'CLUSTERED_INDEX_SEEK', estimated_rows: 25000, cost: 7.44 } ], join_algorithms: [ { type: 'HASH_MATCH', cost: 12.45, estimated_rows: 8500 } ], operators: [ 'Clustered Index Scan', 'Clustered Index Seek', 'Hash Match (Left Outer Join)', 'Stream Aggregate' ], bottlenecks: [ { type: 'LARGE_TABLE_SCAN', severity: 'MEDIUM', description: 'Full table scan on Users table', recommendation: 'Consider adding index on CreatedDate' } ], performance_warnings: [ 'LEFT JOIN with large table may cause performance issues', 'GROUP BY operation requires sorting - consider covering index' ], optimization_suggestions: [ { type: 'INDEX_RECOMMENDATION', priority: 'HIGH', suggestion: 'CREATE INDEX IX_Users_CreatedDate_Name ON Users(CreatedDate) INCLUDE (Name)', estimated_improvement: '60% performance gain' }, { type: 'QUERY_REWRITE', priority: 'MEDIUM', suggestion: 'Consider EXISTS instead of LEFT JOIN if you only need users with orders', estimated_improvement: '25% performance gain' } ] } }, // Optimization insights optimizationInsights: { database_name: 'TestDB', analysis_period: '7_DAYS', overall_health: { score: 72, status: 'NEEDS_ATTENTION', issues_count: 3, critical_issues: 1 }, top_issues: [ { category: 'MISSING_INDEXES', severity: 'CRITICAL', count: 12, impact: 'HIGH', description: 'Multiple high-impact missing indexes detected', estimated_improvement: '50% average query performance improvement' }, { category: 'SLOW_QUERIES', severity: 'HIGH', count: 8, impact: 'MEDIUM', description: 'Queries taking longer than 5 seconds', estimated_improvement: '40% reduction in query time' }, { category: 'BLOCKING_ISSUES', severity: 'MEDIUM', count: 3, impact: 'MEDIUM', description: 'Lock contention and blocking detected', estimated_improvement: '20% improvement in concurrency' } ], recommendations: [ { priority: 'CRITICAL', type: 'INDEX_OPTIMIZATION', action: 'Create 5 high-impact missing indexes', effort: 'LOW', impact: 'HIGH', estimated_benefit: '45% average performance improvement' }, { priority: 'HIGH', type: 'QUERY_TUNING', action: 'Optimize 3 slowest queries', effort: 'MEDIUM', impact: 'HIGH', estimated_benefit: '60% improvement for affected queries' } ], trends: { performance_trend: 'DECLINING', query_volume_trend: 'INCREASING', error_rate_trend: 'STABLE' } } }; beforeEach(async () => { // Set up test environment manually without overriding mock responses setupTestEnvironment(); mcpServer = await createTestMcpServerV4(); mcpServer.pool = mockPool; // Clear previous mock calls but don't set default empty responses mockRequest.query.mockClear(); }); afterEach(() => { resetEnvironment(); }); describe('getIndexRecommendations (TDD - Should Fail Initially)', () => { test('should return index recommendations for a specific database', async () => { // First call: USE [database] (will return undefined/empty) mockRequest.query.mockResolvedValueOnce({ recordset: [] }); // Second call: actual index recommendations query mockRequest.query.mockResolvedValueOnce({ recordset: mockOptimizationData.indexRecommendations }); const result = await mcpServer.getIndexRecommendations('TestDB', 'dbo', 10); const responseData = JSON.parse(result.content[0].text); expect(mockRequest.query).toHaveBeenCalledWith( expect.stringContaining('sys.dm_db_missing_index') ); expect(responseData.database_name).toBe('TestDB'); expect(responseData.schema_name).toBe('dbo'); expect(responseData.recommendations).toHaveLength(2); expect(responseData.recommendations[0].impact_score).toBe(85.7); expect(responseData.recommendations[0].recommended_index).toBe('IX_Users_LastName_FirstName'); }); test('should handle limit parameter for index recommendations', async () => { const limitedData = mockOptimizationData.indexRecommendations.slice(0, 1); // First call: USE [database] mockRequest.query.mockResolvedValueOnce({ recordset: [] }); // Second call: actual index recommendations query mockRequest.query.mockResolvedValueOnce({ recordset: limitedData }); const result = await mcpServer.getIndexRecommendations('TestDB', 'dbo', 1); const responseData = JSON.parse(result.content[0].text); expect(responseData.recommendations).toHaveLength(1); expect(responseData.limit_applied).toBe(1); }); test('should include impact threshold filtering', async () => { // First call: USE [database] mockRequest.query.mockResolvedValueOnce({ recordset: [] }); // Second call: actual index recommendations query with filtering mockRequest.query.mockResolvedValueOnce({ recordset: mockOptimizationData.indexRecommendations.filter(idx => idx.impact_score >= 80) }); const result = await mcpServer.getIndexRecommendations('TestDB', 'dbo', 10, 80.0); const responseData = JSON.parse(result.content[0].text); expect(responseData.recommendations).toHaveLength(1); expect(responseData.impact_threshold).toBe(80.0); expect(responseData.recommendations[0].impact_score).toBeGreaterThanOrEqual(80); }); test('should respect read-only mode security restrictions', async () => { mcpServer.readOnlyMode = true; // First call: USE [database] mockRequest.query.mockResolvedValueOnce({ recordset: [] }); // Second call: actual index recommendations query mockRequest.query.mockResolvedValueOnce({ recordset: [] }); const result = await mcpServer.getIndexRecommendations('TestDB'); const responseData = JSON.parse(result.content[0].text); // Should succeed in read-only mode (analysis only) expect(responseData.security_note).toContain('read-only mode'); expect(responseData.security_info.can_create_indexes).toBe(false); }); }); describe('analyzeQueryPerformance (TDD - Should Fail Initially)', () => { test('should analyze query performance and provide optimization suggestions', async () => { const testQuery = 'SELECT u.Name, COUNT(o.OrderID) FROM Users u LEFT JOIN Orders o ON u.UserID = o.CustomerID WHERE u.CreatedDate >= ? GROUP BY u.Name'; // Mock execution plan query mockRequest.query.mockResolvedValueOnce({ recordset: [ { StmtText: testQuery, TotalSubtreeCost: 15.67, EstimateRows: 8500 } ] }); // Mock query stats mockRequest.query.mockResolvedValueOnce({ recordset: [ { avg_cpu_time: 3200, avg_logical_reads: 25000, avg_duration: 8500, execution_count: 450 } ] }); const result = await mcpServer.analyzeQueryPerformance(testQuery, 'TestDB'); const responseData = JSON.parse(result.content[0].text); expect(responseData.query).toBe(testQuery); expect(responseData.database_name).toBe('TestDB'); expect(responseData.analysis.query_type).toBe('SELECT_WITH_JOIN_AND_AGGREGATION'); expect(responseData.analysis.complexity_score).toBeGreaterThan(0); expect(responseData.analysis.optimization_suggestions).toHaveLength(2); expect(responseData.analysis.optimization_suggestions[0].type).toBe('INDEX_RECOMMENDATION'); }); test('should handle different query types correctly', async () => { // Disable read-only mode to allow INSERT query analysis mcpServer.readOnlyMode = false; mcpServer.allowDestructiveOperations = true; const insertQuery = 'INSERT INTO Users (Name, Email) VALUES (?, ?)'; mockRequest.query.mockResolvedValueOnce({ recordset: [{ StmtText: insertQuery, TotalSubtreeCost: 2.34 }] }); const result = await mcpServer.analyzeQueryPerformance(insertQuery, 'TestDB'); const responseData = JSON.parse(result.content[0].text); expect(responseData.analysis.query_type).toBe('INSERT'); expect(responseData.analysis.is_modification_query).toBe(true); }); test('should validate query safety before analysis', async () => { mcpServer.readOnlyMode = true; const updateQuery = 'UPDATE Users SET LastLogin = GETDATE() WHERE UserID = ?'; await expect(mcpServer.analyzeQueryPerformance(updateQuery, 'TestDB')).rejects.toThrow( 'Query blocked by safety policy' ); // Performance monitoring should handle the error gracefully }); }); describe('detectQueryBottlenecks (TDD - Should Fail Initially)', () => { test('should detect and categorize query bottlenecks', async () => { // First call: USE [database] mockRequest.query.mockResolvedValueOnce({ recordset: [] }); // Second call: actual bottleneck query mockRequest.query.mockResolvedValueOnce({ recordset: mockOptimizationData.queryBottlenecks }); const result = await mcpServer.detectQueryBottlenecks('TestDB', 10, 'CRITICAL'); const responseData = JSON.parse(result.content[0].text); expect(responseData.database_name).toBe('TestDB'); expect(responseData.bottlenecks).toHaveLength(1); expect(responseData.bottlenecks[0].bottleneck_type).toBe('IO_INTENSIVE'); expect(responseData.bottlenecks[0].severity).toBe('CRITICAL'); expect(responseData.bottlenecks[0].wait_stats).toHaveLength(2); expect(responseData.bottlenecks[0].wait_stats[0].wait_type).toBe('PAGEIOLATCH_SH'); }); test('should filter bottlenecks by severity level', async () => { const allBottlenecks = [ ...mockOptimizationData.queryBottlenecks, { ...mockOptimizationData.queryBottlenecks[0], severity: 'MEDIUM', query_hash: 'XYZ789ABC123' } ]; // First call: USE [database] mockRequest.query.mockResolvedValueOnce({ recordset: [] }); // Second call: bottleneck query with filtering mockRequest.query.mockResolvedValueOnce({ recordset: allBottlenecks.filter(b => b.severity === 'HIGH') }); const result = await mcpServer.detectQueryBottlenecks('TestDB', 10, 'HIGH'); const responseData = JSON.parse(result.content[0].text); expect(responseData.severity_filter).toBe('HIGH'); expect(responseData.bottlenecks.every(b => b.severity === 'HIGH')).toBe(true); }); test('should include wait statistics analysis', async () => { // First call: USE [database] mockRequest.query.mockResolvedValueOnce({ recordset: [] }); // Second call: bottleneck query mockRequest.query.mockResolvedValueOnce({ recordset: mockOptimizationData.queryBottlenecks }); const result = await mcpServer.detectQueryBottlenecks('TestDB'); const responseData = JSON.parse(result.content[0].text); const bottleneck = responseData.bottlenecks[0]; expect(bottleneck.wait_stats).toBeDefined(); expect(bottleneck.wait_stats[0].wait_type).toBe('PAGEIOLATCH_SH'); // The wait time is calculated as 30% of avg_duration_ms (8500 * 0.3 = 2550) expect(bottleneck.wait_stats[0].wait_time_ms).toBe(2550); }); }); describe('getOptimizationInsights (TDD - Should Fail Initially)', () => { test('should provide comprehensive database optimization insights', async () => { // Mock multiple queries for insights (USE database + 4 analysis queries) mockRequest.query .mockResolvedValueOnce({ recordset: [] }) // USE [database] .mockResolvedValueOnce({ recordset: [{ total_missing_indexes: 12 }] }) .mockResolvedValueOnce({ recordset: [{ slow_queries_count: 8 }] }) .mockResolvedValueOnce({ recordset: [{ blocking_sessions: 3 }] }) .mockResolvedValueOnce({ recordset: [{ avg_cpu_percent: 65, avg_io_percent: 50 }] }); const result = await mcpServer.getOptimizationInsights('TestDB', '7_DAYS'); const responseData = JSON.parse(result.content[0].text); expect(responseData.database_name).toBe('TestDB'); expect(responseData.analysis_period).toBe('7_DAYS'); expect(responseData.overall_health.score).toBeGreaterThan(0); expect(responseData.overall_health.status).toBeDefined(); expect(responseData.top_issues).toHaveLength(3); expect(responseData.recommendations).toHaveLength(2); expect(responseData.trends).toBeDefined(); }); test('should calculate health score based on multiple factors', async () => { // Mock healthy database stats (USE database + 4 analysis queries) mockRequest.query .mockResolvedValueOnce({ recordset: [] }) // USE [database] .mockResolvedValueOnce({ recordset: [{ total_missing_indexes: 2 }] }) .mockResolvedValueOnce({ recordset: [{ slow_queries_count: 1 }] }) .mockResolvedValueOnce({ recordset: [{ blocking_sessions: 0 }] }) .mockResolvedValueOnce({ recordset: [{ avg_cpu_percent: 25, avg_io_percent: 30 }] }); const result = await mcpServer.getOptimizationInsights('TestDB'); const responseData = JSON.parse(result.content[0].text); expect(responseData.overall_health.score).toBeGreaterThan(80); expect(responseData.overall_health.status).toBe('HEALTHY'); expect(responseData.overall_health.critical_issues).toBe(0); }); test('should support different analysis periods', async () => { // Mock database stats (USE database + 4 analysis queries) mockRequest.query .mockResolvedValueOnce({ recordset: [] }) // USE [database] .mockResolvedValueOnce({ recordset: [{ total_missing_indexes: 5 }] }) .mockResolvedValueOnce({ recordset: [{ slow_queries_count: 3 }] }) .mockResolvedValueOnce({ recordset: [{ blocking_sessions: 1 }] }) .mockResolvedValueOnce({ recordset: [{ avg_cpu_percent: 45, avg_io_percent: 55 }] }); const result = await mcpServer.getOptimizationInsights('TestDB', '24_HOURS'); const responseData = JSON.parse(result.content[0].text); expect(responseData.analysis_period).toBe('24_HOURS'); expect(mockRequest.query).toHaveBeenCalledWith( expect.stringContaining('DATEADD(HOUR, -24, GETDATE())') ); }); }); describe('Security and Safety Compliance (TDD - Should Fail Initially)', () => { test('all optimization tools should respect read-only mode', async () => { mcpServer.readOnlyMode = true; // Mock successful queries for read-only analysis mockRequest.query.mockResolvedValue({ recordset: [] }); // All tools should work in read-only mode (analysis only) await expect(mcpServer.getIndexRecommendations('TestDB')).resolves.not.toThrow(); await expect(mcpServer.detectQueryBottlenecks('TestDB')).resolves.not.toThrow(); await expect(mcpServer.getOptimizationInsights('TestDB')).resolves.not.toThrow(); // analyzeQueryPerformance should work with SELECT queries await expect( mcpServer.analyzeQueryPerformance('SELECT * FROM Users', 'TestDB') ).resolves.not.toThrow(); }); test('should block analysis of dangerous queries in read-only mode', async () => { mcpServer.readOnlyMode = true; const dangerousQuery = 'DROP TABLE Users'; await expect(mcpServer.analyzeQueryPerformance(dangerousQuery, 'TestDB')).rejects.toThrow( 'Query blocked by safety policy' ); }); test('should include security context in all responses', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); const result = await mcpServer.getIndexRecommendations('TestDB'); const responseData = JSON.parse(result.content[0].text); expect(responseData.security_info).toBeDefined(); expect(responseData.security_info.read_only_mode).toBe(mcpServer.readOnlyMode); expect(responseData.security_info.destructive_operations_allowed).toBe( mcpServer.allowDestructiveOperations ); expect(responseData.security_info.schema_changes_allowed).toBe(mcpServer.allowSchemaChanges); }); }); describe('Performance Monitoring Integration (TDD - Should Fail Initially)', () => { test('all optimization tools should integrate with performance monitoring', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.getIndexRecommendations('TestDB'); await mcpServer.detectQueryBottlenecks('TestDB'); await mcpServer.getOptimizationInsights('TestDB'); // Performance monitoring integration tested elsewhere // This test focuses on the tool functionality }); test('should handle performance monitoring errors gracefully', async () => { // This test validates the performance monitor error handling we implemented // The getIndexRecommendations method should work even if performance monitoring fails mockRequest.query.mockResolvedValue({ recordset: [] }); // Tools should still work even if performance monitoring fails await expect(mcpServer.getIndexRecommendations('TestDB')).resolves.not.toThrow(); }); }); describe('Error Handling (TDD - Should Fail Initially)', () => { test('should handle database connection errors', async () => { mockRequest.query.mockRejectedValue(new Error('Database connection lost')); await expect(mcpServer.getIndexRecommendations('TestDB')).rejects.toThrow( 'Failed to get index recommendations: Database connection lost' ); }); test('should handle invalid database names', async () => { mockRequest.query.mockRejectedValue( new Error("Invalid object name 'sys.dm_db_missing_index_details'") ); await expect(mcpServer.getIndexRecommendations('NonExistentDB')).rejects.toThrow( 'Failed to get index recommendations' ); }); test('should validate input parameters', async () => { // Test invalid impact threshold await expect(mcpServer.getIndexRecommendations('TestDB', 'dbo', 10, -5)).rejects.toThrow( 'Impact threshold must be between 0 and 100' ); // Test invalid severity level await expect(mcpServer.detectQueryBottlenecks('TestDB', 10, 'INVALID')).rejects.toThrow( 'Invalid severity level' ); }); }); });

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/egarcia74/warp-sql-server-mcp'

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