Skip to main content
Glama
mcp-server-lifecycle.test.js14.9 kB
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest'; // Mock the mssql module to avoid any real DB interactions vi.mock('mssql', () => ({ default: { connect: vi.fn(), ConnectionPool: vi.fn() }, connect: vi.fn(), ConnectionPool: vi.fn() })); // Mock StdioServerTransport to avoid importing actual MCP SDK transport vi.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({ StdioServerTransport: vi.fn(() => ({ connect: vi.fn() })) })); import { SqlServerMCP } from '../../index.js'; describe('SQL Server MCP Lifecycle', () => { let mcpServer; let mockPool; let mockRequest; const originalEnv = process.env; beforeEach(() => { // Reset environment variables process.env = { ...originalEnv, SQL_SERVER_HOST: 'localhost', SQL_SERVER_PORT: '1433', SQL_SERVER_DATABASE: 'master', SQL_SERVER_USER: 'testuser', SQL_SERVER_PASSWORD: 'testpass' }; // Reset all mocks vi.clearAllMocks(); // Mock SQL pool and request mockRequest = { query: vi.fn(), timeout: 30000 }; mockPool = { request: vi.fn(() => mockRequest), connected: true, close: vi.fn() }; // Create an actual SqlServerMCP instance for testing // Mock the server setup to prevent actual MCP server initialization vi.spyOn(SqlServerMCP.prototype, 'setupToolHandlers').mockImplementation(() => {}); mcpServer = new SqlServerMCP(); mcpServer.pool = null; // Reset pool }); afterEach(() => { process.env = originalEnv; }); describe('Configuration Summary', () => { let originalConsoleError; let consoleErrorSpy; let originalNodeEnv; beforeEach(() => { originalConsoleError = console.error; consoleErrorSpy = vi.fn(); console.error = consoleErrorSpy; // Temporarily disable test mode for these tests originalNodeEnv = process.env.NODE_ENV; process.env.NODE_ENV = 'development'; }); afterEach(() => { console.error = originalConsoleError; process.env.NODE_ENV = originalNodeEnv; }); test('should print secure configuration summary', () => { // Test with default secure configuration mcpServer = new SqlServerMCP(); mcpServer.printConfigurationSummary(); // In test environment, connection fails but shows connection attempt expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('Connection failed to localhost:1433/master') ); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('Security: 🔒 SECURE (RO, DML-, DDL-)') ); // Should not show security warnings for secure config expect(consoleErrorSpy).not.toHaveBeenCalledWith( expect.stringContaining('WARNING: Read-write mode') ); }); test('should print unsafe configuration summary with warnings', () => { // Test with unsafe configuration process.env.SQL_SERVER_READ_ONLY = 'false'; process.env.SQL_SERVER_ALLOW_DESTRUCTIVE_OPERATIONS = 'true'; process.env.SQL_SERVER_ALLOW_SCHEMA_CHANGES = 'true'; mcpServer = new SqlServerMCP(); mcpServer.printConfigurationSummary(); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('Security: ⚠️ UNSAFE (RW, DML+, DDL+)') ); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('WARNING: Read-write mode, DML allowed, DDL allowed') ); }); test('should print mixed configuration correctly', () => { // Test with mixed configuration (read-write but no destructive ops) process.env.SQL_SERVER_READ_ONLY = 'false'; process.env.SQL_SERVER_ALLOW_DESTRUCTIVE_OPERATIONS = 'false'; process.env.SQL_SERVER_ALLOW_SCHEMA_CHANGES = 'false'; mcpServer = new SqlServerMCP(); mcpServer.printConfigurationSummary(); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('Security: ⚠️ UNSAFE (RW, DML-, DDL-)') ); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('WARNING: Read-write mode') ); expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('DML allowed')); }); test('should display correct authentication method for SQL Auth', () => { process.env.SQL_SERVER_USER = 'testuser'; mcpServer = new SqlServerMCP(); mcpServer.printConfigurationSummary(); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('(SQL Auth)')); }); test('should display correct authentication method for Windows Auth', () => { delete process.env.SQL_SERVER_USER; delete process.env.SQL_SERVER_PASSWORD; mcpServer = new SqlServerMCP(); mcpServer.printConfigurationSummary(); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('(Windows Auth)')); }); test('should display custom host and port', () => { process.env.SQL_SERVER_HOST = 'sqlserver.example.com'; process.env.SQL_SERVER_PORT = '1434'; process.env.SQL_SERVER_DATABASE = 'MyDatabase'; mcpServer = new SqlServerMCP(); mcpServer.printConfigurationSummary(); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('Connection failed to sqlserver.example.com:1434/MyDatabase') ); }); test('should not print anything during tests', () => { // Restore test mode for this specific test process.env.NODE_ENV = 'test'; mcpServer = new SqlServerMCP(); mcpServer.printConfigurationSummary(); expect(consoleErrorSpy).not.toHaveBeenCalled(); }); test('should handle partial unsafe configurations correctly', () => { // DML operations enabled, but read-only mode should make it secure process.env.SQL_SERVER_READ_ONLY = 'true'; process.env.SQL_SERVER_ALLOW_DESTRUCTIVE_OPERATIONS = 'true'; process.env.SQL_SERVER_ALLOW_SCHEMA_CHANGES = 'false'; mcpServer = new SqlServerMCP(); mcpServer.printConfigurationSummary(); // The actual behavior shows UNSAFE because DML+ is displayed, but there should be a warning // since read-only mode is enabled (which overrides the DML setting in practice) expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('Security: ⚠️ UNSAFE (RO, DML+, DDL-)') ); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('WARNING: DML allowed')); }); }); describe('Server Startup and Runtime', () => { describe('run() method', () => { let originalConsoleError; let consoleErrorSpy; let originalNodeEnv; let _mockTransport; let mockServer; beforeEach(() => { // Mock console.error to capture startup messages originalConsoleError = console.error; consoleErrorSpy = vi.fn(); console.error = consoleErrorSpy; // Temporarily disable test mode for these tests originalNodeEnv = process.env.NODE_ENV; process.env.NODE_ENV = 'development'; // Mock the transport and server _mockTransport = { connect: vi.fn() }; mockServer = { connect: vi.fn().mockResolvedValue() }; // Replace the server instance mcpServer.server = mockServer; }); afterEach(() => { console.error = originalConsoleError; process.env.NODE_ENV = originalNodeEnv; vi.restoreAllMocks(); }); test('should start server successfully with database connection', async () => { // Mock successful database connection vi.spyOn(mcpServer, 'connectToDatabase').mockResolvedValue(mockPool); await mcpServer.run(); // Verify startup messages expect(consoleErrorSpy).toHaveBeenCalledWith('Starting Warp SQL Server MCP server...'); expect(consoleErrorSpy).toHaveBeenCalledWith( 'Database connection pool initialized successfully' ); expect(consoleErrorSpy).toHaveBeenCalledWith('Warp SQL Server MCP server running on stdio'); // Verify database connection was attempted expect(mcpServer.connectToDatabase).toHaveBeenCalled(); // Verify server connection was called expect(mockServer.connect).toHaveBeenCalled(); }); test('should handle database connection failure gracefully during startup', async () => { const dbError = new Error('Connection refused'); vi.spyOn(mcpServer, 'connectToDatabase').mockRejectedValue(dbError); await mcpServer.run(); // Verify error handling messages expect(consoleErrorSpy).toHaveBeenCalledWith('Starting Warp SQL Server MCP server...'); expect(consoleErrorSpy).toHaveBeenCalledWith( 'Failed to initialize database connection pool:', 'Connection refused' ); expect(consoleErrorSpy).toHaveBeenCalledWith( 'Server will continue but database operations will likely fail' ); expect(consoleErrorSpy).toHaveBeenCalledWith('Warp SQL Server MCP server running on stdio'); // Verify server still starts despite DB failure expect(mockServer.connect).toHaveBeenCalled(); }); test('should handle server connection errors during startup', async () => { const serverError = new Error('Transport connection failed'); vi.spyOn(mcpServer, 'connectToDatabase').mockResolvedValue(mockPool); mockServer.connect.mockRejectedValue(serverError); await expect(mcpServer.run()).rejects.toThrow('Transport connection failed'); // Server connection fails before database connection is attempted expect(mcpServer.connectToDatabase).not.toHaveBeenCalled(); expect(consoleErrorSpy).not.toHaveBeenCalledWith( 'Database connection pool initialized successfully' ); expect(consoleErrorSpy).not.toHaveBeenCalledWith('Starting Warp SQL Server MCP server...'); // Verify server connection was attempted expect(mockServer.connect).toHaveBeenCalled(); }); test('should handle both database and server connection failures', async () => { const dbError = new Error('Database unavailable'); const serverError = new Error('Transport unavailable'); vi.spyOn(mcpServer, 'connectToDatabase').mockRejectedValue(dbError); mockServer.connect.mockRejectedValue(serverError); await expect(mcpServer.run()).rejects.toThrow('Transport unavailable'); // Since server connection fails first, database connection is never attempted expect(mcpServer.connectToDatabase).not.toHaveBeenCalled(); expect(consoleErrorSpy).not.toHaveBeenCalledWith( 'Failed to initialize database connection pool:', 'Database unavailable' ); expect(consoleErrorSpy).not.toHaveBeenCalledWith( 'Server will continue but database operations will likely fail' ); }); }); describe('Entry Point Execution', () => { let originalArgv; let _originalImportMetaUrl; beforeEach(() => { originalArgv = process.argv; // We can't easily mock import.meta.url, so we'll test the logic indirectly }); afterEach(() => { process.argv = originalArgv; }); test('should create and run server when executed as main module', async () => { // Since we can't easily mock import.meta.url in tests, we'll test the // conditional logic by verifying that if the condition were true, // the server would be created and run() called const mockRunMethod = vi.fn().mockResolvedValue(); const originalRun = SqlServerMCP.prototype.run; SqlServerMCP.prototype.run = mockRunMethod; // Create a new instance to simulate entry point execution const entryPointServer = new SqlServerMCP(); // Manually call the entry point logic (simulating the condition being true) await entryPointServer.run(); expect(mockRunMethod).toHaveBeenCalled(); // Restore original method SqlServerMCP.prototype.run = originalRun; }); test('should handle errors in entry point execution', async () => { const originalConsoleError = console.error; const consoleErrorSpy = vi.fn(); console.error = consoleErrorSpy; const runError = new Error('Startup failed'); const mockRunMethod = vi.fn().mockRejectedValue(runError); const originalRun = SqlServerMCP.prototype.run; SqlServerMCP.prototype.run = mockRunMethod; // Create a new instance and simulate error handling const entryPointServer = new SqlServerMCP(); try { await entryPointServer.run(); } catch (error) { // The entry point catches and logs errors expect(error).toBe(runError); } expect(mockRunMethod).toHaveBeenCalled(); // Restore original methods SqlServerMCP.prototype.run = originalRun; console.error = originalConsoleError; }); }); describe('Integration Scenarios', () => { let integrationConsoleErrorSpy; let originalNodeEnv; beforeEach(() => { // Set up console spy for integration tests integrationConsoleErrorSpy = vi.fn(); console.error = integrationConsoleErrorSpy; // Temporarily disable test mode for these tests originalNodeEnv = process.env.NODE_ENV; process.env.NODE_ENV = 'development'; }); afterEach(() => { process.env.NODE_ENV = originalNodeEnv; }); test('should handle complete startup flow with all components', async () => { // Mock successful database connection vi.spyOn(mcpServer, 'connectToDatabase').mockResolvedValue(mockPool); // Mock successful transport connection const mockTransport = { connect: vi.fn() }; const mockServer = { connect: vi.fn().mockResolvedValue() }; mcpServer.server = mockServer; // Mock StdioServerTransport constructor to return our mock vi.doMock('@modelcontextprotocol/sdk/server/stdio.js', () => ({ StdioServerTransport: vi.fn(() => mockTransport) })); await mcpServer.run(); // Verify complete startup sequence - at minimum these messages expect(integrationConsoleErrorSpy).toHaveBeenCalledWith( 'Starting Warp SQL Server MCP server...' ); expect(integrationConsoleErrorSpy).toHaveBeenCalledWith( 'Database connection pool initialized successfully' ); expect(integrationConsoleErrorSpy).toHaveBeenCalledWith( 'Warp SQL Server MCP server running on stdio' ); }); }); }); });

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