#!/usr/bin/env node /** * Integration Tests for Pake CLI * * These tests verify that different components work together correctly. * They may take longer to run as they test actual build processes. */ import { spawn, execSync } from 'child_process'; import fs from 'fs'; import path from 'path'; import config, { TIMEOUTS, TEST_URLS } from './test.config.js'; import ora from 'ora'; class IntegrationTestRunner { constructor() { this.tests = []; this.results = []; this.tempFiles = []; } addTest(name, testFn, timeout = TIMEOUTS.MEDIUM) { this.tests.push({ name, testFn, timeout }); } async runAll() { console.log('šŸ”§ Integration Tests'); console.log('====================\n'); for (const [index, test] of this.tests.entries()) { const spinner = ora(`Running ${test.name}...`).start(); try { const result = await Promise.race([ test.testFn(), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), test.timeout) ) ]); if (result) { spinner.succeed(`${index + 1}. ${test.name}: PASS`); this.results.push({ name: test.name, passed: true }); } else { spinner.fail(`${index + 1}. ${test.name}: FAIL`); this.results.push({ name: test.name, passed: false }); } } catch (error) { spinner.fail(`${index + 1}. ${test.name}: ERROR - ${error.message.slice(0, 50)}...`); this.results.push({ name: test.name, passed: false, error: error.message }); } } this.cleanup(); this.displayResults(); } cleanup() { // Clean up any temporary files created during tests this.tempFiles.forEach(file => { try { if (fs.existsSync(file)) { fs.unlinkSync(file); } } catch (error) { console.warn(`Warning: Could not clean up ${file}`); } }); } displayResults() { const passed = this.results.filter(r => r.passed).length; const total = this.results.length; console.log(`\nšŸ“Š Integration Test Results: ${passed}/${total} passed\n`); if (passed === total) { console.log('šŸŽ‰ All integration tests passed!'); } else { console.log('āŒ Some integration tests failed'); this.results.filter(r => !r.passed).forEach(result => { console.log(` - ${result.name}${result.error ? `: ${result.error}` : ''}`); }); } } trackTempFile(filepath) { this.tempFiles.push(filepath); } } const runner = new IntegrationTestRunner(); // Integration Tests runner.addTest( 'CLI Process Spawning', () => { return new Promise((resolve) => { const child = spawn('node', [config.CLI_PATH, '--version'], { stdio: ['pipe', 'pipe', 'pipe'] }); let output = ''; child.stdout.on('data', (data) => { output += data.toString(); }); child.on('close', (code) => { resolve(code === 0 && /\d+\.\d+\.\d+/.test(output)); }); // Kill after 3 seconds if still running setTimeout(() => { child.kill(); resolve(false); }, 3000); }); } ); runner.addTest( 'Interactive Mode Simulation', () => { return new Promise((resolve) => { const child = spawn('node', [config.CLI_PATH, TEST_URLS.WEEKLY], { stdio: ['pipe', 'pipe', 'pipe'] }); let output = ''; let prompted = false; child.stdout.on('data', (data) => { output += data.toString(); // If we see a prompt for application name, provide input if (output.includes('Enter your application name') && !prompted) { prompted = true; child.stdin.write('TestApp\n'); setTimeout(() => { child.kill(); resolve(true); }, 1000); } }); child.on('close', () => { resolve(prompted); }); // Timeout after 10 seconds setTimeout(() => { child.kill(); resolve(false); }, 10000); }); }, TIMEOUTS.MEDIUM ); runner.addTest( 'Command Line Argument Parsing', () => { try { // Test argument validation by running CLI with --help to verify args are parsed const helpOutput = execSync(`node "${config.CLI_PATH}" --help`, { encoding: 'utf8', timeout: 3000 }); // Verify that our command structure is valid by checking help includes our options const validOptions = ['--width', '--height', '--debug', '--name'].every(opt => helpOutput.includes(opt) ); return validOptions; } catch (error) { return false; } }, TIMEOUTS.QUICK ); runner.addTest( 'File System Permissions', () => { try { // Test that we can write to current directory const testFile = 'test-write-permission.tmp'; fs.writeFileSync(testFile, 'test'); runner.trackTempFile(testFile); // Test that we can read from CLI directory const cliStats = fs.statSync(config.CLI_PATH); return cliStats.isFile(); } catch (error) { return false; } } ); runner.addTest( 'Dependency Resolution', () => { try { // Verify that essential runtime dependencies are available const packageJsonPath = path.join(config.PROJECT_ROOT, 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const essentialDeps = [ 'commander', 'chalk', 'fs-extra', 'execa', 'prompts' ]; return essentialDeps.every(dep => { try { // Try to resolve the dependency import.meta.resolve ? import.meta.resolve(dep) : require.resolve(dep, { paths: [config.PROJECT_ROOT] }); return true; } catch { return packageJson.dependencies && packageJson.dependencies[dep]; } }); } catch { return false; } } ); // Run integration tests if this file is executed directly if (import.meta.url === `file://${process.argv[1]}`) { runner.runAll().catch(console.error); } export default runner;