Skip to main content
Glama

n8n-MCP

by 88-888
database-adapter-unit.test.tsβ€’10.6 kB
import { describe, it, expect, vi } from 'vitest'; // Mock logger vi.mock('../../../src/utils/logger', () => ({ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() } })); describe('Database Adapter - Unit Tests', () => { describe('DatabaseAdapter Interface', () => { it('should define interface when adapter is created', () => { // This is a type test - ensuring the interface is correctly defined type DatabaseAdapter = { prepare: (sql: string) => any; exec: (sql: string) => void; close: () => void; pragma: (key: string, value?: any) => any; readonly inTransaction: boolean; transaction: <T>(fn: () => T) => T; checkFTS5Support: () => boolean; }; // Type assertion to ensure interface matches const mockAdapter: DatabaseAdapter = { prepare: vi.fn(), exec: vi.fn(), close: vi.fn(), pragma: vi.fn(), inTransaction: false, transaction: vi.fn((fn) => fn()), checkFTS5Support: vi.fn(() => true) }; expect(mockAdapter).toBeDefined(); expect(mockAdapter.prepare).toBeDefined(); expect(mockAdapter.exec).toBeDefined(); expect(mockAdapter.close).toBeDefined(); expect(mockAdapter.pragma).toBeDefined(); expect(mockAdapter.transaction).toBeDefined(); expect(mockAdapter.checkFTS5Support).toBeDefined(); }); }); describe('PreparedStatement Interface', () => { it('should define interface when statement is prepared', () => { // Type test for PreparedStatement type PreparedStatement = { run: (...params: any[]) => { changes: number; lastInsertRowid: number | bigint }; get: (...params: any[]) => any; all: (...params: any[]) => any[]; iterate: (...params: any[]) => IterableIterator<any>; pluck: (toggle?: boolean) => PreparedStatement; expand: (toggle?: boolean) => PreparedStatement; raw: (toggle?: boolean) => PreparedStatement; columns: () => any[]; bind: (...params: any[]) => PreparedStatement; }; const mockStmt: PreparedStatement = { run: vi.fn(() => ({ changes: 1, lastInsertRowid: 1 })), get: vi.fn(), all: vi.fn(() => []), iterate: vi.fn(function* () {}), pluck: vi.fn(function(this: any) { return this; }), expand: vi.fn(function(this: any) { return this; }), raw: vi.fn(function(this: any) { return this; }), columns: vi.fn(() => []), bind: vi.fn(function(this: any) { return this; }) }; expect(mockStmt).toBeDefined(); expect(mockStmt.run).toBeDefined(); expect(mockStmt.get).toBeDefined(); expect(mockStmt.all).toBeDefined(); expect(mockStmt.iterate).toBeDefined(); expect(mockStmt.pluck).toBeDefined(); expect(mockStmt.expand).toBeDefined(); expect(mockStmt.raw).toBeDefined(); expect(mockStmt.columns).toBeDefined(); expect(mockStmt.bind).toBeDefined(); }); }); describe('FTS5 Support Detection', () => { it('should detect support when FTS5 module is available', () => { const mockDb = { exec: vi.fn() }; // Function to test FTS5 support detection logic const checkFTS5Support = (db: any): boolean => { try { db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS test_fts5 USING fts5(content);"); db.exec("DROP TABLE IF EXISTS test_fts5;"); return true; } catch (error) { return false; } }; // Test when FTS5 is supported expect(checkFTS5Support(mockDb)).toBe(true); expect(mockDb.exec).toHaveBeenCalledWith( "CREATE VIRTUAL TABLE IF NOT EXISTS test_fts5 USING fts5(content);" ); // Test when FTS5 is not supported mockDb.exec.mockImplementation(() => { throw new Error('no such module: fts5'); }); expect(checkFTS5Support(mockDb)).toBe(false); }); }); describe('Transaction Handling', () => { it('should handle commit and rollback when transaction is executed', () => { // Test transaction wrapper logic const mockDb = { exec: vi.fn(), inTransaction: false }; const transaction = <T>(db: any, fn: () => T): T => { try { db.exec('BEGIN'); db.inTransaction = true; const result = fn(); db.exec('COMMIT'); db.inTransaction = false; return result; } catch (error) { db.exec('ROLLBACK'); db.inTransaction = false; throw error; } }; // Test successful transaction const result = transaction(mockDb, () => 'success'); expect(result).toBe('success'); expect(mockDb.exec).toHaveBeenCalledWith('BEGIN'); expect(mockDb.exec).toHaveBeenCalledWith('COMMIT'); expect(mockDb.inTransaction).toBe(false); // Reset mocks mockDb.exec.mockClear(); // Test failed transaction expect(() => { transaction(mockDb, () => { throw new Error('transaction error'); }); }).toThrow('transaction error'); expect(mockDb.exec).toHaveBeenCalledWith('BEGIN'); expect(mockDb.exec).toHaveBeenCalledWith('ROLLBACK'); expect(mockDb.inTransaction).toBe(false); }); }); describe('Pragma Handling', () => { it('should return values when pragma commands are executed', () => { const mockDb = { pragma: vi.fn((key: string, value?: any) => { if (key === 'journal_mode' && value === 'WAL') { return 'wal'; } return null; }) }; expect(mockDb.pragma('journal_mode', 'WAL')).toBe('wal'); expect(mockDb.pragma('other_key')).toBe(null); }); }); describe('SQLJSAdapter Save Behavior (Memory Leak Fix - Issue #330)', () => { it('should use default 5000ms save interval when env var not set', () => { // Verify default interval is 5000ms (not old 100ms) const DEFAULT_INTERVAL = 5000; expect(DEFAULT_INTERVAL).toBe(5000); }); it('should use custom save interval from SQLJS_SAVE_INTERVAL_MS env var', () => { // Mock environment variable const originalEnv = process.env.SQLJS_SAVE_INTERVAL_MS; process.env.SQLJS_SAVE_INTERVAL_MS = '10000'; // Test that interval would be parsed const envInterval = process.env.SQLJS_SAVE_INTERVAL_MS; const parsedInterval = envInterval ? parseInt(envInterval, 10) : 5000; expect(parsedInterval).toBe(10000); // Restore environment if (originalEnv !== undefined) { process.env.SQLJS_SAVE_INTERVAL_MS = originalEnv; } else { delete process.env.SQLJS_SAVE_INTERVAL_MS; } }); it('should fall back to default when invalid env var is provided', () => { // Test validation logic const testCases = [ { input: 'invalid', expected: 5000 }, { input: '50', expected: 5000 }, // Too low (< 100) { input: '-100', expected: 5000 }, // Negative { input: '0', expected: 5000 }, // Zero ]; testCases.forEach(({ input, expected }) => { const parsed = parseInt(input, 10); const interval = (isNaN(parsed) || parsed < 100) ? 5000 : parsed; expect(interval).toBe(expected); }); }); it('should debounce multiple rapid saves using configured interval', () => { // Test debounce logic let timer: NodeJS.Timeout | null = null; const mockSave = vi.fn(); const scheduleSave = (interval: number) => { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { mockSave(); }, interval); }; // Simulate rapid operations scheduleSave(5000); scheduleSave(5000); scheduleSave(5000); // Should only schedule once (debounced) expect(mockSave).not.toHaveBeenCalled(); // Cleanup if (timer) clearTimeout(timer); }); }); describe('SQLJSAdapter Memory Optimization', () => { it('should not use Buffer.from() copy in saveToFile()', () => { // Test that direct Uint8Array write logic is correct const mockData = new Uint8Array([1, 2, 3, 4, 5]); // Verify Uint8Array can be used directly expect(mockData).toBeInstanceOf(Uint8Array); expect(mockData.length).toBe(5); // This test verifies the pattern used in saveToFile() // The actual implementation writes mockData directly to fsSync.writeFileSync() // without using Buffer.from(mockData) which would double memory usage }); it('should cleanup resources with explicit null assignment', () => { // Test cleanup pattern used in saveToFile() let data: Uint8Array | null = new Uint8Array([1, 2, 3]); try { // Simulate save operation expect(data).not.toBeNull(); } finally { // Explicit cleanup helps GC data = null; } expect(data).toBeNull(); }); it('should handle save errors without leaking resources', () => { // Test error handling with cleanup let data: Uint8Array | null = null; let errorThrown = false; try { data = new Uint8Array([1, 2, 3]); // Simulate error throw new Error('Save failed'); } catch (error) { errorThrown = true; } finally { // Cleanup happens even on error data = null; } expect(errorThrown).toBe(true); expect(data).toBeNull(); }); }); describe('Read vs Write Operation Handling', () => { it('should not trigger save on read-only prepare() calls', () => { // Test that prepare() doesn't schedule save // Only exec() and SQLJSStatement.run() should trigger saves const mockScheduleSave = vi.fn(); // Simulate prepare() - should NOT call scheduleSave // prepare() just creates statement, doesn't modify DB // Simulate exec() - SHOULD call scheduleSave mockScheduleSave(); expect(mockScheduleSave).toHaveBeenCalledTimes(1); }); it('should trigger save on write operations (INSERT/UPDATE/DELETE)', () => { const mockScheduleSave = vi.fn(); // Simulate write operations mockScheduleSave(); // INSERT mockScheduleSave(); // UPDATE mockScheduleSave(); // DELETE expect(mockScheduleSave).toHaveBeenCalledTimes(3); }); }); });

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/88-888/n8n-mcp'

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