Skip to main content
Glama
connection-manager.test.js10.8 kB
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest'; import { setupEnvironment, _resetEnvironment, createMockPool, cleanupMocks } from './fixtures/modern-fixtures.js'; // Mock mssql module vi.mock('mssql', () => ({ default: { connect: vi.fn(), ConnectionPool: vi.fn() }, connect: vi.fn() })); describe('ConnectionManager', () => { let connectionManager; let mockSql; let mockPool; let mockConfig; beforeEach(async () => { setupEnvironment({ SQL_SERVER_HOST: 'localhost', SQL_SERVER_PORT: '1433', SQL_SERVER_DATABASE: 'testdb', SQL_SERVER_USER: 'testuser', SQL_SERVER_PASSWORD: 'testpass' }); // Reset modules to pick up environment changes vi.resetModules(); mockPool = createMockPool(); mockSql = await import('mssql'); mockSql.default.connect.mockResolvedValue(mockPool); mockConfig = { connectionTimeout: 10000, requestTimeout: 30000, maxRetries: 3, retryDelay: 1000 }; const { ConnectionManager } = await import('../../lib/database/connection-manager.js'); connectionManager = new ConnectionManager(mockConfig); }); afterEach(() => { cleanupMocks(); }); describe('connect', () => { test('should connect successfully with SQL Server authentication', async () => { const pool = await connectionManager.connect(); expect(mockSql.default.connect).toHaveBeenCalledWith({ server: 'localhost', port: 1433, database: 'testdb', user: 'testuser', password: 'testpass', options: { encrypt: false, trustServerCertificate: true, enableArithAbort: true, requestTimeout: 30000, connectionTimeout: 10000 } }); expect(pool).toBe(mockPool); expect(connectionManager.getPool()).toBe(mockPool); }); test('should connect with Windows authentication when no credentials provided', async () => { setupEnvironment({ SQL_SERVER_HOST: 'localhost', SQL_SERVER_PORT: '1433', SQL_SERVER_DATABASE: 'testdb', SQL_SERVER_USER: '', SQL_SERVER_PASSWORD: '', SQL_SERVER_DOMAIN: 'TESTDOMAIN' }); vi.resetModules(); const { ConnectionManager } = await import('../../lib/database/connection-manager.js'); connectionManager = new ConnectionManager(mockConfig); await connectionManager.connect(); expect(mockSql.default.connect).toHaveBeenCalledWith({ server: 'localhost', port: 1433, database: 'testdb', authentication: { type: 'ntlm', options: { domain: 'TESTDOMAIN' } }, options: { encrypt: false, trustServerCertificate: true, enableArithAbort: true, requestTimeout: 30000, connectionTimeout: 10000 } }); }); test('should reuse existing connection when already connected', async () => { // First connection const pool1 = await connectionManager.connect(); // Second connection should reuse the same pool const pool2 = await connectionManager.connect(); expect(mockSql.default.connect).toHaveBeenCalledTimes(1); expect(pool1).toBe(pool2); }); test('should handle connection timeout', async () => { const timeoutError = new Error('Connection timeout'); timeoutError.code = 'ETIMEOUT'; mockSql.default.connect.mockRejectedValue(timeoutError); await expect(connectionManager.connect()).rejects.toThrow( 'Failed to connect to SQL Server after 3 attempts: Connection timeout' ); expect(mockSql.default.connect).toHaveBeenCalledTimes(3); }); test('should handle authentication failures', async () => { const authError = new Error('Login failed'); authError.code = 'ELOGIN'; mockSql.default.connect.mockRejectedValue(authError); await expect(connectionManager.connect()).rejects.toThrow( 'Failed to connect to SQL Server after 3 attempts: Login failed' ); }); test('should handle server not found errors', async () => { const serverError = new Error('Server not found'); serverError.code = 'ENOTFOUND'; mockSql.default.connect.mockRejectedValue(serverError); await expect(connectionManager.connect()).rejects.toThrow( 'Failed to connect to SQL Server after 3 attempts: Server not found' ); }); test('should retry on transient failures', async () => { const transientError = new Error('Connection lost'); transientError.code = 'ECONNRESET'; // Fail twice, then succeed mockSql.default.connect .mockRejectedValueOnce(transientError) .mockRejectedValueOnce(transientError) .mockResolvedValueOnce(mockPool); const pool = await connectionManager.connect(); expect(mockSql.default.connect).toHaveBeenCalledTimes(3); expect(pool).toBe(mockPool); }); test('should not retry on non-transient failures', async () => { const authError = new Error('Invalid credentials'); authError.code = 'ELOGIN'; mockSql.default.connect.mockRejectedValue(authError); await expect(connectionManager.connect()).rejects.toThrow(); // Should fail immediately without retries for auth errors expect(mockSql.default.connect).toHaveBeenCalledTimes(1); }); test('should apply retry delay between attempts', async () => { const { ConnectionManager } = await import('../../lib/database/connection-manager.js'); const connectionManager = new ConnectionManager({ ...mockConfig, retryDelay: 50 // Use shorter delay for testing }); const transientError = new Error('Connection lost'); transientError.code = 'ECONNRESET'; mockSql.default.connect.mockRejectedValue(transientError); const startTime = Date.now(); try { await connectionManager.connect(); } catch { // Expected to fail } const duration = Date.now() - startTime; // Should have taken at least 100ms (2 retries * 50ms delay) expect(duration).toBeGreaterThanOrEqual(90); // Allow some variance expect(mockSql.default.connect).toHaveBeenCalledTimes(3); }); }); describe('getPool', () => { test('should return current pool when connected', async () => { await connectionManager.connect(); const pool = connectionManager.getPool(); expect(pool).toBe(mockPool); }); test('should return null when not connected', () => { const pool = connectionManager.getPool(); expect(pool).toBeNull(); }); }); describe('isConnectionActive', () => { test('should return true when pool is connected', async () => { await connectionManager.connect(); expect(connectionManager.isConnectionActive()).toBe(true); }); test('should return false when pool is not connected', async () => { await connectionManager.connect(); mockPool.connected = false; expect(connectionManager.isConnectionActive()).toBe(false); }); test('should return false when no pool exists', () => { expect(connectionManager.isConnectionActive()).toBe(false); }); }); describe('close', () => { test('should close active connection', async () => { await connectionManager.connect(); await connectionManager.close(); expect(mockPool.close).toHaveBeenCalled(); expect(connectionManager.getPool()).toBeNull(); }); test('should handle close when no connection exists', async () => { // Should not throw when closing non-existent connection await expect(connectionManager.close()).resolves.not.toThrow(); }); test('should handle pool close errors gracefully', async () => { await connectionManager.connect(); mockPool.close.mockRejectedValue(new Error('Close failed')); // Should not throw even if close fails await expect(connectionManager.close()).resolves.not.toThrow(); expect(connectionManager.getPool()).toBeNull(); }); }); describe('getConnectionHealth', () => { test('should return health status when connected', async () => { await connectionManager.connect(); const health = connectionManager.getConnectionHealth(); expect(health).toEqual({ connected: true, status: 'Connected', pool: { size: expect.any(Number), available: expect.any(Number), pending: expect.any(Number), borrowed: expect.any(Number) } }); }); test('should return disconnected status when not connected', () => { const health = connectionManager.getConnectionHealth(); expect(health).toEqual({ connected: false, status: 'Disconnected', pool: null }); }); test('should handle pool without health info', async () => { // Create pool without health properties const simplePool = { connected: true, close: vi.fn() }; mockSql.default.connect.mockResolvedValue(simplePool); await connectionManager.connect(); const health = connectionManager.getConnectionHealth(); expect(health.connected).toBe(true); expect(health.status).toBe('Connected'); expect(health.pool).toEqual({ size: 0, available: 0, pending: 0, borrowed: 0 }); }); }); describe('configuration handling', () => { test('should use default configuration when no config provided', async () => { const { ConnectionManager } = await import('../../lib/database/connection-manager.js'); const defaultConnectionManager = new ConnectionManager(); await defaultConnectionManager.connect(); expect(mockSql.default.connect).toHaveBeenCalledWith( expect.objectContaining({ options: expect.objectContaining({ requestTimeout: 30000, connectionTimeout: 15000 }) }) ); }); test('should merge custom config with defaults', async () => { const customConfig = { connectionTimeout: 5000, requestTimeout: 10000 }; const { ConnectionManager } = await import('../../lib/database/connection-manager.js'); const customConnectionManager = new ConnectionManager(customConfig); await customConnectionManager.connect(); expect(mockSql.default.connect).toHaveBeenCalledWith( expect.objectContaining({ options: expect.objectContaining({ requestTimeout: 10000, connectionTimeout: 5000 }) }) ); }); }); });

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