Skip to main content
Glama
mcp-data-tools.test.js22 kB
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest'; import { setupMssqlMock, setupStdioMock, setupMcpTest, resetEnvironment, createTestMcpServerV4, mockPool, mockRequest, testData } from './mcp-shared-fixtures.js'; // Setup module mocks setupMssqlMock(); setupStdioMock(); describe('Data Tools', () => { let mcpServer; beforeEach(async () => { setupMcpTest(); mcpServer = await createTestMcpServerV4(); mcpServer.pool = mockPool; }); afterEach(() => { resetEnvironment(); }); describe('getTableData', () => { test('should get table data with default limit', async () => { mockRequest.query.mockResolvedValue({ recordset: testData.sampleTableData }); const result = await mcpServer.getTableData('Users'); expect(mockRequest.query).toHaveBeenCalledWith('SELECT TOP 100 * FROM [dbo].[Users]'); const responseData = JSON.parse(result.content[0].text); expect(responseData.table).toBe('dbo.Users'); expect(responseData.rowCount).toBe(3); expect(responseData.data).toEqual(testData.sampleTableData); }); test('should apply WHERE clause when provided', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.getTableData('Users', null, 'dbo', 100, 'id > 10'); expect(mockRequest.query).toHaveBeenCalledWith( 'SELECT TOP 100 * FROM [dbo].[Users] WHERE id > 10' ); }); test('should handle complex WHERE clauses with AND conditions', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.getTableData( 'Users', null, 'dbo', 50, "status = 'active' AND created_date > '2023-01-01'" ); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT TOP 50 * FROM [dbo].[Users] WHERE status = 'active' AND created_date > '2023-01-01'" ); }); test('should handle LIKE pattern matching in WHERE clause', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.getTableData('Users', null, 'dbo', 100, "name LIKE '%john%'"); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT TOP 100 * FROM [dbo].[Users] WHERE name LIKE '%john%'" ); }); test('should handle NULL checks in WHERE clause', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.getTableData('Users', null, 'dbo', 100, 'deleted_at IS NULL'); expect(mockRequest.query).toHaveBeenCalledWith( 'SELECT TOP 100 * FROM [dbo].[Users] WHERE deleted_at IS NULL' ); }); test('should handle OR conditions in WHERE clause', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.getTableData( 'Users', null, 'dbo', 100, "status = 'active' OR status = 'pending'" ); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT TOP 100 * FROM [dbo].[Users] WHERE status = 'active' OR status = 'pending'" ); }); test('should handle numeric comparisons in WHERE clause', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.getTableData('Users', null, 'dbo', 100, 'age >= 18 AND age <= 65'); expect(mockRequest.query).toHaveBeenCalledWith( 'SELECT TOP 100 * FROM [dbo].[Users] WHERE age >= 18 AND age <= 65' ); }); test('should handle date comparisons in WHERE clause', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.getTableData('Users', null, 'dbo', 100, "created_date > '2023-01-01'"); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT TOP 100 * FROM [dbo].[Users] WHERE created_date > '2023-01-01'" ); }); test('should handle IN clause filtering', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.getTableData( 'Users', null, 'dbo', 100, "status IN ('active', 'pending', 'verified')" ); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT TOP 100 * FROM [dbo].[Users] WHERE status IN ('active', 'pending', 'verified')" ); }); test('should return filtered results correctly', async () => { const filteredData = [ { id: 5, name: 'Alice', status: 'active' }, { id: 8, name: 'Bob', status: 'active' } ]; mockRequest.query.mockResolvedValue({ recordset: filteredData }); const result = await mcpServer.getTableData('Users', null, 'dbo', 100, "status = 'active'"); const responseData = JSON.parse(result.content[0].text); expect(responseData.table).toBe('dbo.Users'); expect(responseData.rowCount).toBe(2); expect(responseData.data).toEqual(filteredData); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT TOP 100 * FROM [dbo].[Users] WHERE status = 'active'" ); }); }); describe('explainQuery', () => { test('should generate execution plan for query', async () => { const mockPlan = [ { StmtText: 'SELECT * FROM Users', StmtId: 1, NodeId: 1, Parent: 0, PhysicalOp: 'Clustered Index Scan', LogicalOp: 'Clustered Index Scan', EstimateCPU: 0.001, EstimateIO: 0.003125, EstimateRows: 100 } ]; // Mock the sequence of queries for explain plan mockRequest.query .mockResolvedValueOnce({ recordset: [], recordsets: [[]] }) // SET SHOWPLAN_ALL ON .mockResolvedValueOnce({ recordset: mockPlan, recordsets: [mockPlan] }) // The actual query plan .mockResolvedValueOnce({ recordset: [], recordsets: [[]] }) // SET SHOWPLAN_ALL OFF .mockResolvedValueOnce({ recordset: [], recordsets: [[]] }); // Cost query const result = await mcpServer.explainQuery('SELECT * FROM Users'); expect(mockRequest.query).toHaveBeenCalledWith('SET SHOWPLAN_ALL ON'); expect(mockRequest.query).toHaveBeenCalledWith('SELECT * FROM Users'); expect(mockRequest.query).toHaveBeenCalledWith('SET SHOWPLAN_ALL OFF'); const responseData = JSON.parse(result.content[0].text); expect(responseData.query).toBe('SELECT * FROM Users'); expect(responseData.execution_plan).toEqual(mockPlan); expect(responseData.plan_type).toBe('estimated'); }); test('should generate actual execution plan when requested', async () => { const mockPlan = [ { StmtText: 'SELECT * FROM Users', ActualRows: 95, ActualExecutions: 1, ActualCPU: 0.002, ActualIO: 0.003 } ]; mockRequest.query .mockResolvedValueOnce({ recordset: [], recordsets: [[]] }) // SET STATISTICS IO ON .mockResolvedValueOnce({ recordset: [], recordsets: [[]] }) // SET STATISTICS TIME ON .mockResolvedValueOnce({ recordset: [], recordsets: [[]] }) // SET SHOWPLAN_ALL ON .mockResolvedValueOnce({ recordset: mockPlan, recordsets: [mockPlan] }) // The actual query plan .mockResolvedValueOnce({ recordset: [], recordsets: [[]] }) // SET SHOWPLAN_ALL OFF .mockResolvedValueOnce({ recordset: [], recordsets: [[]] }) // SET STATISTICS IO OFF .mockResolvedValueOnce({ recordset: [], recordsets: [[]] }) // SET STATISTICS TIME OFF .mockResolvedValueOnce({ recordset: [], recordsets: [[]] }); // Cost query const result = await mcpServer.explainQuery('SELECT * FROM Users', null, true); expect(mockRequest.query).toHaveBeenCalledWith('SET STATISTICS IO ON'); expect(mockRequest.query).toHaveBeenCalledWith('SET STATISTICS TIME ON'); expect(mockRequest.query).toHaveBeenCalledWith('SET SHOWPLAN_ALL ON'); const responseData = JSON.parse(result.content[0].text); expect(responseData.plan_type).toBe('actual'); expect(responseData.execution_plan).toEqual(mockPlan); }); test('should switch database when specified', async () => { mockRequest.query.mockResolvedValue({ recordset: [], recordsets: [[]] }); await mcpServer.explainQuery('SELECT 1', 'TestDB'); expect(mockRequest.query).toHaveBeenCalledWith('USE [TestDB]'); }); test('should handle explain query errors', async () => { const error = new Error('Query plan generation failed'); mockRequest.query.mockRejectedValue(error); await expect(mcpServer.explainQuery('INVALID SQL')).rejects.toThrow( 'Failed to explain query: Query plan generation failed' ); }); }); describe('listForeignKeys', () => { beforeEach(() => { // Mock performance monitoring for listForeignKeys mcpServer.performanceMonitor = { startQuery: vi.fn(() => 'query_id_123'), endQuery: vi.fn() }; }); test('should list foreign key relationships', async () => { const mockForeignKeys = [ { constraint_name: 'FK_Orders_Users', parent_table: 'Orders', parent_column: 'user_id', referenced_table: 'Users', referenced_column: 'id', on_delete: 'CASCADE', on_update: 'NO ACTION', is_disabled: false }, { constraint_name: 'FK_OrderItems_Orders', parent_table: 'OrderItems', parent_column: 'order_id', referenced_table: 'Orders', referenced_column: 'id', on_delete: 'CASCADE', on_update: 'NO ACTION', is_disabled: false } ]; mockRequest.query.mockResolvedValue({ recordset: mockForeignKeys }); const result = await mcpServer.listForeignKeys(); expect(mockRequest.query).toHaveBeenCalledWith(expect.stringContaining('sys.foreign_keys')); expect(mockRequest.query).toHaveBeenCalledWith( expect.stringContaining("WHERE s.name = 'dbo'") ); const responseData = JSON.parse(result.content[0].text); expect(responseData.schema).toBe('dbo'); expect(responseData.foreign_keys).toEqual(mockForeignKeys); expect(responseData.count).toBe(2); }); test('should switch database when specified', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.listForeignKeys('TestDB', 'custom'); expect(mockRequest.query).toHaveBeenNthCalledWith(1, 'USE [TestDB]'); }); test('should handle custom schema', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.listForeignKeys(null, 'custom'); expect(mockRequest.query).toHaveBeenCalledWith( expect.stringContaining("WHERE s.name = 'custom'") ); }); }); describe('exportTableCsv', () => { beforeEach(() => { // Mock performance monitoring for exportTableCsv mcpServer.performanceMonitor = { startQuery: vi.fn(() => 'query_id_123'), endQuery: vi.fn() }; }); test('should export table data as CSV', async () => { const mockData = [ { id: 1, name: 'John Doe', email: 'john@example.com' }, { id: 2, name: 'Jane Smith', email: 'jane@example.com' } ]; mockRequest.query.mockResolvedValue({ recordset: mockData }); const result = await mcpServer.exportTableCsv('Users'); expect(mockRequest.query).toHaveBeenCalledWith('SELECT * FROM [dbo].[Users]'); const responseData = JSON.parse(result.content[0].text); expect(responseData.table).toBe('dbo.Users'); expect(responseData.format).toBe('csv'); expect(responseData.row_count).toBe(2); expect(responseData.column_count).toBe(3); expect(responseData.csv_data).toContain('id,name,email'); expect(responseData.csv_data).toContain('1,John Doe,john@example.com'); expect(responseData.csv_data).toContain('2,Jane Smith,jane@example.com'); }); test('should apply limit when specified', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.exportTableCsv('Users', null, 'dbo', 50); expect(mockRequest.query).toHaveBeenCalledWith('SELECT TOP 50 * FROM [dbo].[Users]'); }); test('should apply WHERE clause when specified', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.exportTableCsv('Users', null, 'dbo', null, 'active = 1'); expect(mockRequest.query).toHaveBeenCalledWith( 'SELECT * FROM [dbo].[Users] WHERE active = 1' ); }); test('should handle CSV escaping correctly', async () => { const mockData = [ { id: 1, description: 'Text with, comma', notes: 'Text with "quotes"' }, { id: 2, description: 'Text with\nnewline', notes: null } ]; mockRequest.query.mockResolvedValue({ recordset: mockData }); const result = await mcpServer.exportTableCsv('TestTable'); const responseData = JSON.parse(result.content[0].text); expect(responseData.csv_data).toContain('"Text with, comma"'); expect(responseData.csv_data).toContain('"Text with ""quotes"""'); // The newline character should be handled by the CSV generation logic expect(responseData.csv_data).toContain('2,"Text with\nnewline",'); }); test('should handle empty table', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); const result = await mcpServer.exportTableCsv('EmptyTable'); const responseData = JSON.parse(result.content[0].text); expect(responseData.row_count).toBe(0); expect(responseData.csv_data).toBe(''); }); test('should switch database when specified', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); await mcpServer.exportTableCsv('Users', 'TestDB'); expect(mockRequest.query).toHaveBeenNthCalledWith(1, 'USE [TestDB]'); }); describe('CSV Export Filtering Tests', () => { test('should handle simple WHERE clause in CSV export', async () => { const mockData = [{ id: 5, name: 'Active User', status: 'active' }]; mockRequest.query.mockResolvedValue({ recordset: mockData }); const result = await mcpServer.exportTableCsv( 'Users', null, 'dbo', null, "status = 'active'" ); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT * FROM [dbo].[Users] WHERE status = 'active'" ); const responseData = JSON.parse(result.content[0].text); expect(responseData.row_count).toBe(1); expect(responseData.csv_data).toContain('5,Active User,active'); }); test('should handle complex AND/OR conditions in CSV export', async () => { const mockData = [ { id: 1, name: 'Alice', status: 'active', age: 25 }, { id: 2, name: 'Bob', status: 'active', age: 30 } ]; mockRequest.query.mockResolvedValue({ recordset: mockData }); await mcpServer.exportTableCsv('Users', null, 'dbo', 100, "status = 'active' AND age > 20"); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT TOP 100 * FROM [dbo].[Users] WHERE status = 'active' AND age > 20" ); }); test('should handle LIKE patterns in CSV export WHERE clause', async () => { const mockData = [{ id: 1, name: 'John Doe', email: 'john@example.com' }]; mockRequest.query.mockResolvedValue({ recordset: mockData }); await mcpServer.exportTableCsv('Users', null, 'dbo', null, "email LIKE '%@example.com'"); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT * FROM [dbo].[Users] WHERE email LIKE '%@example.com'" ); }); test('should handle NULL checks in CSV export WHERE clause', async () => { const mockData = [{ id: 1, name: 'Active User', deleted_at: null }]; mockRequest.query.mockResolvedValue({ recordset: mockData }); await mcpServer.exportTableCsv('Users', null, 'dbo', null, 'deleted_at IS NULL'); expect(mockRequest.query).toHaveBeenCalledWith( 'SELECT * FROM [dbo].[Users] WHERE deleted_at IS NULL' ); }); test('should handle date range filtering in CSV export', async () => { const mockData = [{ id: 1, name: 'Recent User', created_date: '2023-06-15' }]; mockRequest.query.mockResolvedValue({ recordset: mockData }); await mcpServer.exportTableCsv( 'Users', null, 'dbo', null, "created_date BETWEEN '2023-01-01' AND '2023-12-31'" ); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT * FROM [dbo].[Users] WHERE created_date BETWEEN '2023-01-01' AND '2023-12-31'" ); }); test('should handle IN clause filtering in CSV export', async () => { const mockData = [ { id: 1, name: 'Admin User', role: 'admin' }, { id: 2, name: 'Manager User', role: 'manager' } ]; mockRequest.query.mockResolvedValue({ recordset: mockData }); await mcpServer.exportTableCsv( 'Users', null, 'dbo', null, "role IN ('admin', 'manager', 'supervisor')" ); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT * FROM [dbo].[Users] WHERE role IN ('admin', 'manager', 'supervisor')" ); }); test('should combine WHERE clause with LIMIT correctly in CSV export', async () => { const mockData = [ { id: 10, name: 'User 10', status: 'active' }, { id: 20, name: 'User 20', status: 'active' } ]; mockRequest.query.mockResolvedValue({ recordset: mockData }); await mcpServer.exportTableCsv('Users', null, 'dbo', 50, "status = 'active'"); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT TOP 50 * FROM [dbo].[Users] WHERE status = 'active'" ); const result = await mcpServer.exportTableCsv( 'Users', null, 'dbo', 50, "status = 'active'" ); const responseData = JSON.parse(result.content[0].text); expect(responseData.row_count).toBe(2); }); test('should handle empty results from filtered CSV export', async () => { mockRequest.query.mockResolvedValue({ recordset: [] }); const result = await mcpServer.exportTableCsv( 'Users', null, 'dbo', null, "status = 'nonexistent'" ); expect(mockRequest.query).toHaveBeenCalledWith( "SELECT * FROM [dbo].[Users] WHERE status = 'nonexistent'" ); const responseData = JSON.parse(result.content[0].text); expect(responseData.row_count).toBe(0); expect(responseData.csv_data).toBe(''); }); }); describe('CSV Export Error Handling', () => { test('should handle database connection errors during CSV export', async () => { // Simulate a database connection failure mockPool.request.mockImplementation(() => { throw new Error('Database connection lost'); }); mcpServer.pool = mockPool; await expect(mcpServer.exportTableCsv('Users', null, 'dbo', null, null)).rejects.toThrow( 'Failed to export table as CSV: Database connection lost' ); }); test('should handle SQL query errors during CSV export', async () => { const mockRequestWithError = { ...mockRequest, timeout: 30000, query: vi.fn().mockRejectedValue(new Error("Invalid object name 'NonExistentTable'")) }; mockPool.request.mockReturnValue(mockRequestWithError); mcpServer.pool = mockPool; await expect( mcpServer.exportTableCsv('NonExistentTable', null, 'dbo', null, null) ).rejects.toThrow("Failed to export table as CSV: Invalid object name 'NonExistentTable'"); }); test('should handle permission errors during CSV export', async () => { const mockRequestWithError = { ...mockRequest, timeout: 30000, query: vi .fn() .mockRejectedValue(new Error("SELECT permission denied on object 'SecureTable'")) }; mockPool.request.mockReturnValue(mockRequestWithError); mcpServer.pool = mockPool; await expect( mcpServer.exportTableCsv('SecureTable', null, 'dbo', null, null) ).rejects.toThrow( "Failed to export table as CSV: SELECT permission denied on object 'SecureTable'" ); }); test('should handle timeout errors during CSV export', async () => { const mockRequestWithError = { ...mockRequest, timeout: 30000, query: vi.fn().mockRejectedValue(new Error('Timeout expired')) }; mockPool.request.mockReturnValue(mockRequestWithError); mcpServer.pool = mockPool; await expect( mcpServer.exportTableCsv('LargeTable', null, 'dbo', 1000000, null) ).rejects.toThrow('Failed to export table as CSV: Timeout expired'); }); test('should handle invalid WHERE clause syntax errors during CSV export', async () => { const mockRequestWithError = { ...mockRequest, timeout: 30000, query: vi.fn().mockRejectedValue(new Error("Incorrect syntax near 'INVALID'")) }; mockPool.request.mockReturnValue(mockRequestWithError); mcpServer.pool = mockPool; await expect( mcpServer.exportTableCsv('Users', null, 'dbo', null, 'INVALID SYNTAX HERE') ).rejects.toThrow("Failed to export table as CSV: Incorrect syntax near 'INVALID'"); }); }); }); });

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