mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-06 05:23:08 +00:00
feat: add comprehensive performance benchmark tracking system
- Create benchmark test suites for critical operations: - Node loading performance - Database query performance - Search operations performance - Validation performance - MCP tool execution performance - Add GitHub Actions workflow for benchmark tracking: - Runs on push to main and PRs - Uses github-action-benchmark for historical tracking - Comments on PRs with performance results - Alerts on >10% performance regressions - Stores results in GitHub Pages - Create benchmark infrastructure: - Custom Vitest benchmark configuration - JSON reporter for CI results - Result formatter for github-action-benchmark - Performance threshold documentation - Add supporting utilities: - SQLiteStorageService for benchmark database setup - MCPEngine wrapper for testing MCP tools - Test factories for generating benchmark data - Enhanced NodeRepository with benchmark methods - Document benchmark system: - Comprehensive benchmark guide in docs/BENCHMARKS.md - Performance thresholds in .github/BENCHMARK_THRESHOLDS.md - README for benchmarks directory - Integration with existing test suite The benchmark system will help monitor performance over time and catch regressions before they reach production. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
342
tests/setup/test-env.ts
Normal file
342
tests/setup/test-env.ts
Normal file
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* Test Environment Configuration Loader
|
||||
*
|
||||
* This module handles loading and validating test environment variables
|
||||
* with type safety and default values.
|
||||
*/
|
||||
|
||||
import * as dotenv from 'dotenv';
|
||||
import * as path from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
// Load test environment variables
|
||||
export function loadTestEnvironment(): void {
|
||||
// Load base test environment
|
||||
const testEnvPath = path.resolve(process.cwd(), '.env.test');
|
||||
if (existsSync(testEnvPath)) {
|
||||
dotenv.config({ path: testEnvPath });
|
||||
}
|
||||
|
||||
// Load local test overrides (for sensitive values)
|
||||
const localEnvPath = path.resolve(process.cwd(), '.env.test.local');
|
||||
if (existsSync(localEnvPath)) {
|
||||
dotenv.config({ path: localEnvPath, override: true });
|
||||
}
|
||||
|
||||
// Set test-specific defaults
|
||||
setTestDefaults();
|
||||
|
||||
// Validate required environment variables
|
||||
validateTestEnvironment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default values for test environment variables
|
||||
*/
|
||||
function setTestDefaults(): void {
|
||||
// Ensure we're in test mode
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.TEST_ENVIRONMENT = 'true';
|
||||
|
||||
// Set defaults if not already set
|
||||
const defaults: Record<string, string> = {
|
||||
// Database
|
||||
NODE_DB_PATH: ':memory:',
|
||||
REBUILD_ON_START: 'false',
|
||||
|
||||
// API
|
||||
N8N_API_URL: 'http://localhost:3001/mock-api',
|
||||
N8N_API_KEY: 'test-api-key',
|
||||
|
||||
// Server
|
||||
PORT: '3001',
|
||||
HOST: '127.0.0.1',
|
||||
|
||||
// Logging
|
||||
LOG_LEVEL: 'error',
|
||||
DEBUG: 'false',
|
||||
TEST_LOG_VERBOSE: 'false',
|
||||
|
||||
// Timeouts
|
||||
TEST_TIMEOUT_UNIT: '5000',
|
||||
TEST_TIMEOUT_INTEGRATION: '15000',
|
||||
TEST_TIMEOUT_E2E: '30000',
|
||||
TEST_TIMEOUT_GLOBAL: '60000',
|
||||
|
||||
// Test execution
|
||||
TEST_RETRY_ATTEMPTS: '2',
|
||||
TEST_RETRY_DELAY: '1000',
|
||||
TEST_PARALLEL: 'true',
|
||||
TEST_MAX_WORKERS: '4',
|
||||
|
||||
// Features
|
||||
FEATURE_MOCK_EXTERNAL_APIS: 'true',
|
||||
FEATURE_USE_TEST_CONTAINERS: 'false',
|
||||
MSW_ENABLED: 'true',
|
||||
MSW_API_DELAY: '0',
|
||||
|
||||
// Paths
|
||||
TEST_FIXTURES_PATH: './tests/fixtures',
|
||||
TEST_DATA_PATH: './tests/data',
|
||||
TEST_SNAPSHOTS_PATH: './tests/__snapshots__',
|
||||
|
||||
// Performance
|
||||
PERF_THRESHOLD_API_RESPONSE: '100',
|
||||
PERF_THRESHOLD_DB_QUERY: '50',
|
||||
PERF_THRESHOLD_NODE_PARSE: '200',
|
||||
|
||||
// Caching
|
||||
CACHE_TTL: '0',
|
||||
CACHE_ENABLED: 'false',
|
||||
|
||||
// Rate limiting
|
||||
RATE_LIMIT_MAX: '0',
|
||||
RATE_LIMIT_WINDOW: '0',
|
||||
|
||||
// Error handling
|
||||
ERROR_SHOW_STACK: 'true',
|
||||
ERROR_SHOW_DETAILS: 'true',
|
||||
|
||||
// Cleanup
|
||||
TEST_CLEANUP_ENABLED: 'true',
|
||||
TEST_CLEANUP_ON_FAILURE: 'false',
|
||||
|
||||
// Database seeding
|
||||
TEST_SEED_DATABASE: 'true',
|
||||
TEST_SEED_TEMPLATES: 'true',
|
||||
|
||||
// Network
|
||||
NETWORK_TIMEOUT: '5000',
|
||||
NETWORK_RETRY_COUNT: '0',
|
||||
|
||||
// Memory
|
||||
TEST_MEMORY_LIMIT: '512',
|
||||
|
||||
// Coverage
|
||||
COVERAGE_DIR: './coverage',
|
||||
COVERAGE_REPORTER: 'lcov,html,text-summary'
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(defaults)) {
|
||||
if (!process.env[key]) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that required environment variables are set
|
||||
*/
|
||||
function validateTestEnvironment(): void {
|
||||
const required = [
|
||||
'NODE_ENV',
|
||||
'NODE_DB_PATH',
|
||||
'N8N_API_URL',
|
||||
'N8N_API_KEY'
|
||||
];
|
||||
|
||||
const missing = required.filter(key => !process.env[key]);
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new Error(
|
||||
`Missing required test environment variables: ${missing.join(', ')}\n` +
|
||||
'Please ensure .env.test is properly configured.'
|
||||
);
|
||||
}
|
||||
|
||||
// Validate NODE_ENV is set to test
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
throw new Error(
|
||||
'NODE_ENV must be set to "test" when running tests.\n' +
|
||||
'This prevents accidental execution against production systems.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get typed test environment configuration
|
||||
*/
|
||||
export function getTestConfig() {
|
||||
return {
|
||||
// Environment
|
||||
nodeEnv: process.env.NODE_ENV!,
|
||||
isTest: process.env.TEST_ENVIRONMENT === 'true',
|
||||
|
||||
// Database
|
||||
database: {
|
||||
path: process.env.NODE_DB_PATH!,
|
||||
rebuildOnStart: process.env.REBUILD_ON_START === 'true',
|
||||
seedData: process.env.TEST_SEED_DATABASE === 'true',
|
||||
seedTemplates: process.env.TEST_SEED_TEMPLATES === 'true'
|
||||
},
|
||||
|
||||
// API
|
||||
api: {
|
||||
url: process.env.N8N_API_URL!,
|
||||
key: process.env.N8N_API_KEY!,
|
||||
webhookBaseUrl: process.env.N8N_WEBHOOK_BASE_URL,
|
||||
webhookTestUrl: process.env.N8N_WEBHOOK_TEST_URL
|
||||
},
|
||||
|
||||
// Server
|
||||
server: {
|
||||
port: parseInt(process.env.PORT || '3001', 10),
|
||||
host: process.env.HOST || '127.0.0.1',
|
||||
corsOrigin: process.env.CORS_ORIGIN?.split(',') || []
|
||||
},
|
||||
|
||||
// Authentication
|
||||
auth: {
|
||||
token: process.env.AUTH_TOKEN,
|
||||
mcpToken: process.env.MCP_AUTH_TOKEN
|
||||
},
|
||||
|
||||
// Logging
|
||||
logging: {
|
||||
level: process.env.LOG_LEVEL || 'error',
|
||||
debug: process.env.DEBUG === 'true',
|
||||
verbose: process.env.TEST_LOG_VERBOSE === 'true',
|
||||
showStack: process.env.ERROR_SHOW_STACK === 'true',
|
||||
showDetails: process.env.ERROR_SHOW_DETAILS === 'true'
|
||||
},
|
||||
|
||||
// Test execution
|
||||
execution: {
|
||||
timeouts: {
|
||||
unit: parseInt(process.env.TEST_TIMEOUT_UNIT || '5000', 10),
|
||||
integration: parseInt(process.env.TEST_TIMEOUT_INTEGRATION || '15000', 10),
|
||||
e2e: parseInt(process.env.TEST_TIMEOUT_E2E || '30000', 10),
|
||||
global: parseInt(process.env.TEST_TIMEOUT_GLOBAL || '60000', 10)
|
||||
},
|
||||
retry: {
|
||||
attempts: parseInt(process.env.TEST_RETRY_ATTEMPTS || '2', 10),
|
||||
delay: parseInt(process.env.TEST_RETRY_DELAY || '1000', 10)
|
||||
},
|
||||
parallel: process.env.TEST_PARALLEL === 'true',
|
||||
maxWorkers: parseInt(process.env.TEST_MAX_WORKERS || '4', 10)
|
||||
},
|
||||
|
||||
// Features
|
||||
features: {
|
||||
coverage: process.env.FEATURE_TEST_COVERAGE === 'true',
|
||||
screenshots: process.env.FEATURE_TEST_SCREENSHOTS === 'true',
|
||||
videos: process.env.FEATURE_TEST_VIDEOS === 'true',
|
||||
trace: process.env.FEATURE_TEST_TRACE === 'true',
|
||||
mockExternalApis: process.env.FEATURE_MOCK_EXTERNAL_APIS === 'true',
|
||||
useTestContainers: process.env.FEATURE_USE_TEST_CONTAINERS === 'true'
|
||||
},
|
||||
|
||||
// Mocking
|
||||
mocking: {
|
||||
msw: {
|
||||
enabled: process.env.MSW_ENABLED === 'true',
|
||||
apiDelay: parseInt(process.env.MSW_API_DELAY || '0', 10)
|
||||
},
|
||||
redis: {
|
||||
enabled: process.env.REDIS_MOCK_ENABLED === 'true',
|
||||
port: parseInt(process.env.REDIS_MOCK_PORT || '6380', 10)
|
||||
},
|
||||
elasticsearch: {
|
||||
enabled: process.env.ELASTICSEARCH_MOCK_ENABLED === 'true',
|
||||
port: parseInt(process.env.ELASTICSEARCH_MOCK_PORT || '9201', 10)
|
||||
}
|
||||
},
|
||||
|
||||
// Paths
|
||||
paths: {
|
||||
fixtures: process.env.TEST_FIXTURES_PATH || './tests/fixtures',
|
||||
data: process.env.TEST_DATA_PATH || './tests/data',
|
||||
snapshots: process.env.TEST_SNAPSHOTS_PATH || './tests/__snapshots__'
|
||||
},
|
||||
|
||||
// Performance
|
||||
performance: {
|
||||
thresholds: {
|
||||
apiResponse: parseInt(process.env.PERF_THRESHOLD_API_RESPONSE || '100', 10),
|
||||
dbQuery: parseInt(process.env.PERF_THRESHOLD_DB_QUERY || '50', 10),
|
||||
nodeParse: parseInt(process.env.PERF_THRESHOLD_NODE_PARSE || '200', 10)
|
||||
}
|
||||
},
|
||||
|
||||
// Rate limiting
|
||||
rateLimiting: {
|
||||
max: parseInt(process.env.RATE_LIMIT_MAX || '0', 10),
|
||||
window: parseInt(process.env.RATE_LIMIT_WINDOW || '0', 10)
|
||||
},
|
||||
|
||||
// Caching
|
||||
cache: {
|
||||
enabled: process.env.CACHE_ENABLED === 'true',
|
||||
ttl: parseInt(process.env.CACHE_TTL || '0', 10)
|
||||
},
|
||||
|
||||
// Cleanup
|
||||
cleanup: {
|
||||
enabled: process.env.TEST_CLEANUP_ENABLED === 'true',
|
||||
onFailure: process.env.TEST_CLEANUP_ON_FAILURE === 'true'
|
||||
},
|
||||
|
||||
// Network
|
||||
network: {
|
||||
timeout: parseInt(process.env.NETWORK_TIMEOUT || '5000', 10),
|
||||
retryCount: parseInt(process.env.NETWORK_RETRY_COUNT || '0', 10)
|
||||
},
|
||||
|
||||
// Memory
|
||||
memory: {
|
||||
limit: parseInt(process.env.TEST_MEMORY_LIMIT || '512', 10)
|
||||
},
|
||||
|
||||
// Coverage
|
||||
coverage: {
|
||||
dir: process.env.COVERAGE_DIR || './coverage',
|
||||
reporters: (process.env.COVERAGE_REPORTER || 'lcov,html,text-summary').split(',')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Export type for the test configuration
|
||||
export type TestConfig = ReturnType<typeof getTestConfig>;
|
||||
|
||||
/**
|
||||
* Helper to check if we're in test mode
|
||||
*/
|
||||
export function isTestMode(): boolean {
|
||||
return process.env.NODE_ENV === 'test' || process.env.TEST_ENVIRONMENT === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get timeout for specific test type
|
||||
*/
|
||||
export function getTestTimeout(type: 'unit' | 'integration' | 'e2e' | 'global' = 'unit'): number {
|
||||
const config = getTestConfig();
|
||||
return config.execution.timeouts[type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to check if a feature is enabled
|
||||
*/
|
||||
export function isFeatureEnabled(feature: keyof TestConfig['features']): boolean {
|
||||
const config = getTestConfig();
|
||||
return config.features[feature];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset environment to defaults (useful for test isolation)
|
||||
*/
|
||||
export function resetTestEnvironment(): void {
|
||||
// Clear all test-specific environment variables
|
||||
const testKeys = Object.keys(process.env).filter(key =>
|
||||
key.startsWith('TEST_') ||
|
||||
key.startsWith('FEATURE_') ||
|
||||
key.startsWith('MSW_') ||
|
||||
key.startsWith('PERF_')
|
||||
);
|
||||
|
||||
testKeys.forEach(key => {
|
||||
delete process.env[key];
|
||||
});
|
||||
|
||||
// Reload defaults
|
||||
loadTestEnvironment();
|
||||
}
|
||||
Reference in New Issue
Block a user