Skip to main content
Glama

n8n-MCP

by 88-888
http-server-auth.test.tsβ€’7.9 kB
import { readFileSync, writeFileSync, mkdirSync, rmSync } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import type { MockedFunction } from 'vitest'; // Import the actual functions we'll be testing import { loadAuthToken, startFixedHTTPServer } from '../src/http-server'; // Mock dependencies vi.mock('../src/utils/logger', () => ({ logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() }, Logger: vi.fn().mockImplementation(() => ({ info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() })), LogLevel: { ERROR: 0, WARN: 1, INFO: 2, DEBUG: 3 } })); vi.mock('dotenv'); // Mock other dependencies to prevent side effects vi.mock('../src/mcp/server', () => ({ N8NDocumentationMCPServer: vi.fn().mockImplementation(() => ({ executeTool: vi.fn() })) })); vi.mock('../src/mcp/tools', () => ({ n8nDocumentationToolsFinal: [] })); vi.mock('../src/mcp/tools-n8n-manager', () => ({ n8nManagementTools: [] })); vi.mock('../src/utils/version', () => ({ PROJECT_VERSION: '2.7.4' })); vi.mock('../src/config/n8n-api', () => ({ isN8nApiConfigured: vi.fn().mockReturnValue(false) })); vi.mock('../src/utils/url-detector', () => ({ getStartupBaseUrl: vi.fn().mockReturnValue('http://localhost:3000'), formatEndpointUrls: vi.fn().mockReturnValue({ health: 'http://localhost:3000/health', mcp: 'http://localhost:3000/mcp' }), detectBaseUrl: vi.fn().mockReturnValue('http://localhost:3000') })); // Create mock server instance const mockServer = { on: vi.fn(), close: vi.fn((callback) => callback()) }; // Mock Express to prevent server from starting const mockExpressApp = { use: vi.fn(), get: vi.fn(), post: vi.fn(), listen: vi.fn((port: any, host: any, callback: any) => { // Call the callback immediately to simulate server start if (callback) callback(); return mockServer; }), set: vi.fn() }; vi.mock('express', () => { const express: any = vi.fn(() => mockExpressApp); express.json = vi.fn(); express.urlencoded = vi.fn(); express.static = vi.fn(); express.Request = {}; express.Response = {}; express.NextFunction = {}; return { default: express }; }); describe('HTTP Server Authentication', () => { const originalEnv = process.env; let tempDir: string; let authTokenFile: string; beforeEach(() => { // Reset modules and environment vi.clearAllMocks(); vi.resetModules(); process.env = { ...originalEnv }; // Create temporary directory for test files tempDir = join(tmpdir(), `http-server-auth-test-${Date.now()}`); mkdirSync(tempDir, { recursive: true }); authTokenFile = join(tempDir, 'auth-token'); }); afterEach(() => { // Restore original environment process.env = originalEnv; // Clean up temporary directory try { rmSync(tempDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } }); describe('loadAuthToken', () => { it('should load token when AUTH_TOKEN environment variable is set', () => { process.env.AUTH_TOKEN = 'test-token-from-env'; delete process.env.AUTH_TOKEN_FILE; const token = loadAuthToken(); expect(token).toBe('test-token-from-env'); }); it('should load token from file when only AUTH_TOKEN_FILE is set', () => { delete process.env.AUTH_TOKEN; process.env.AUTH_TOKEN_FILE = authTokenFile; // Write test token to file writeFileSync(authTokenFile, 'test-token-from-file\n'); const token = loadAuthToken(); expect(token).toBe('test-token-from-file'); }); it('should trim whitespace when reading token from file', () => { delete process.env.AUTH_TOKEN; process.env.AUTH_TOKEN_FILE = authTokenFile; // Write token with whitespace writeFileSync(authTokenFile, ' test-token-with-spaces \n\n'); const token = loadAuthToken(); expect(token).toBe('test-token-with-spaces'); }); it('should prefer AUTH_TOKEN when both variables are set', () => { process.env.AUTH_TOKEN = 'env-token'; process.env.AUTH_TOKEN_FILE = authTokenFile; writeFileSync(authTokenFile, 'file-token'); const token = loadAuthToken(); expect(token).toBe('env-token'); }); it('should return null when AUTH_TOKEN_FILE points to non-existent file', async () => { delete process.env.AUTH_TOKEN; process.env.AUTH_TOKEN_FILE = join(tempDir, 'non-existent-file'); // Import logger to check calls const { logger } = await import('../src/utils/logger'); // Clear any previous mock calls vi.clearAllMocks(); const token = loadAuthToken(); expect(token).toBeNull(); expect(logger.error).toHaveBeenCalled(); const errorCall = (logger.error as MockedFunction<any>).mock.calls[0]; expect(errorCall[0]).toContain('Failed to read AUTH_TOKEN_FILE'); // Check that the second argument exists and is truthy (the error object) expect(errorCall[1]).toBeTruthy(); }); it('should return null when no auth variables are set', () => { delete process.env.AUTH_TOKEN; delete process.env.AUTH_TOKEN_FILE; const token = loadAuthToken(); expect(token).toBeNull(); }); }); describe('validateEnvironment', () => { it('should exit process when no auth token is available', async () => { delete process.env.AUTH_TOKEN; delete process.env.AUTH_TOKEN_FILE; const mockExit = vi.spyOn(process, 'exit').mockImplementation((code?: string | number | null | undefined) => { throw new Error('Process exited'); }); // validateEnvironment is called when starting the server await expect(async () => { await startFixedHTTPServer(); }).rejects.toThrow('Process exited'); expect(mockExit).toHaveBeenCalledWith(1); mockExit.mockRestore(); }); it('should warn when token length is less than 32 characters', async () => { process.env.AUTH_TOKEN = 'short-token'; // Import logger to check calls const { logger } = await import('../src/utils/logger'); // Clear any previous mock calls vi.clearAllMocks(); // Ensure the mock server is properly configured mockExpressApp.listen.mockReturnValue(mockServer); mockServer.on.mockReturnValue(undefined); // Start the server which will trigger validateEnvironment await startFixedHTTPServer(); expect(logger.warn).toHaveBeenCalledWith( 'AUTH_TOKEN should be at least 32 characters for security' ); }); }); describe('Integration test scenarios', () => { it('should authenticate successfully when token is loaded from file', () => { // This is more of an integration test placeholder // In a real scenario, you'd start the server and make HTTP requests writeFileSync(authTokenFile, 'very-secure-token-with-more-than-32-characters'); process.env.AUTH_TOKEN_FILE = authTokenFile; delete process.env.AUTH_TOKEN; const token = loadAuthToken(); expect(token).toBe('very-secure-token-with-more-than-32-characters'); }); it('should load token when using Docker secrets pattern', () => { // Docker secrets are typically mounted at /run/secrets/ const dockerSecretPath = join(tempDir, 'run', 'secrets', 'auth_token'); mkdirSync(join(tempDir, 'run', 'secrets'), { recursive: true }); writeFileSync(dockerSecretPath, 'docker-secret-token'); process.env.AUTH_TOKEN_FILE = dockerSecretPath; delete process.env.AUTH_TOKEN; const token = loadAuthToken(); expect(token).toBe('docker-secret-token'); }); }); });

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