Files
makemd/scripts/run-tests.js
wurenzhi 0dac26d781 feat: 添加MSW模拟服务和数据源集成
refactor: 重构页面组件移除冗余Layout组件

feat: 实现WebSocket和事件总线系统

feat: 添加队列和调度系统

docs: 更新架构文档和服务映射

style: 清理重复接口定义使用数据源

chore: 更新依赖项配置

feat: 添加运行时系统和领域引导

ci: 配置ESLint边界检查规则

build: 添加Redis和WebSocket依赖

test: 添加MSW浏览器环境入口

perf: 优化数据获取逻辑使用统一数据源

fix: 修复类型定义和状态管理问题
2026-03-19 01:39:34 +08:00

789 lines
24 KiB
JavaScript

#!/usr/bin/env node
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const COLORS = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
};
function log(message, color = COLORS.reset) {
console.log(`${color}${message}${COLORS.reset}`);
}
function logStep(step, message) {
console.log(`\n${COLORS.cyan}[${step}]${COLORS.reset} ${message}`);
}
function logSuccess(message) {
console.log(`${COLORS.green}${COLORS.reset} ${message}`);
}
function logError(message) {
console.log(`${COLORS.red}${COLORS.reset} ${message}`);
}
function logWarning(message) {
console.log(`${COLORS.yellow}⚠️${COLORS.reset} ${message}`);
}
function executeCommand(command, description) {
try {
logStep('EXEC', description);
const result = execSync(command, {
encoding: 'utf8',
stdio: 'inherit'
});
logSuccess(description);
return true;
} catch (error) {
logError(`${description} failed: ${error.message}`);
return false;
}
}
function runUnitTests() {
logStep(1, 'Running Unit Tests');
try {
const testResults = {
server: runServerUnitTests(),
dashboard: runDashboardUnitTests(),
client: runClientUnitTests(),
extension: runExtensionUnitTests()
};
const totalTests = Object.values(testResults).reduce((sum, result) => sum + result.total, 0);
const passedTests = Object.values(testResults).reduce((sum, result) => sum + result.passed, 0);
const failedTests = Object.values(testResults).reduce((sum, result) => sum + result.failed, 0);
console.log(`\n${COLORS.cyan}Unit Tests Summary:${COLORS.reset}`);
console.log(` Total: ${totalTests}`);
console.log(` ${COLORS.green}Passed: ${passedTests}${COLORS.reset}`);
console.log(` ${COLORS.red}Failed: ${failedTests}${COLORS.reset}`);
console.log(` Success Rate: ${((passedTests / totalTests) * 100).toFixed(2)}%`);
return failedTests === 0;
} catch (error) {
logError(`Unit tests failed: ${error.message}`);
return false;
}
}
function runServerUnitTests() {
try {
const output = execSync('cd server && npm test -- --coverage --json', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = JSON.parse(output);
const total = results.numTotalTests;
const passed = results.numPassedTests;
const failed = results.numFailedTests;
console.log(`\n${COLORS.cyan}Server Unit Tests:${COLORS.reset}`);
console.log(` Total: ${total}, Passed: ${passed}, Failed: ${failed}`);
console.log(` Coverage: ${results.coverageMap ? 'Generated' : 'Not available'}`);
return { total, passed, failed };
} catch (error) {
logError('Server unit tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runDashboardUnitTests() {
try {
const output = execSync('cd dashboard && npm test -- --coverage --json', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = JSON.parse(output);
const total = results.numTotalTests;
const passed = results.numPassedTests;
const failed = results.numFailedTests;
console.log(`\n${COLORS.cyan}Dashboard Unit Tests:${COLORS.reset}`);
console.log(` Total: ${total}, Passed: ${passed}, Failed: ${failed}`);
console.log(` Coverage: ${results.coverageMap ? 'Generated' : 'Not available'}`);
return { total, passed, failed };
} catch (error) {
logError('Dashboard unit tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runClientUnitTests() {
try {
const output = execSync('cd client && npm test -- --coverage --json', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = JSON.parse(output);
const total = results.numTotalTests;
const passed = results.numPassedTests;
const failed = results.numFailedTests;
console.log(`\n${COLORS.cyan}Client Unit Tests:${COLORS.reset}`);
console.log(` Total: ${total}, Passed: ${passed}, Failed: ${failed}`);
console.log(` Coverage: ${results.coverageMap ? 'Generated' : 'Not available'}`);
return { total, passed, failed };
} catch (error) {
logError('Client unit tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runExtensionUnitTests() {
try {
const output = execSync('cd extension && npm test -- --coverage --json', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = JSON.parse(output);
const total = results.numTotalTests;
const passed = results.numPassedTests;
const failed = results.numFailedTests;
console.log(`\n${COLORS.cyan}Extension Unit Tests:${COLORS.reset}`);
console.log(` Total: ${total}, Passed: ${passed}, Failed: ${failed}`);
console.log(` Coverage: ${results.coverageMap ? 'Generated' : 'Not available'}`);
return { total, passed, failed };
} catch (error) {
logError('Extension unit tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runIntegrationTests() {
logStep(2, 'Running Integration Tests');
try {
const testResults = {
api: runAPIIntegrationTests(),
database: runDatabaseIntegrationTests(),
redis: runRedisIntegrationTests(),
websocket: runWebSocketIntegrationTests()
};
const totalTests = Object.values(testResults).reduce((sum, result) => sum + result.total, 0);
const passedTests = Object.values(testResults).reduce((sum, result) => sum + result.passed, 0);
const failedTests = Object.values(testResults).reduce((sum, result) => sum + result.failed, 0);
console.log(`\n${COLORS.cyan}Integration Tests Summary:${COLORS.reset}`);
console.log(` Total: ${totalTests}`);
console.log(` ${COLORS.green}Passed: ${passedTests}${COLORS.reset}`);
console.log(` ${COLORS.red}Failed: ${failedTests}${COLORS.reset}`);
console.log(` Success Rate: ${((passedTests / totalTests) * 100).toFixed(2)}%`);
return failedTests === 0;
} catch (error) {
logError(`Integration tests failed: ${error.message}`);
return false;
}
}
function runAPIIntegrationTests() {
try {
const output = execSync('cd server && npm run test:integration', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}API Integration Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('API integration tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runDatabaseIntegrationTests() {
try {
const output = execSync('cd server && npm run test:database', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Database Integration Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Database integration tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runRedisIntegrationTests() {
try {
const output = execSync('cd server && npm run test:redis', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Redis Integration Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Redis integration tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runWebSocketIntegrationTests() {
try {
const output = execSync('cd server && npm run test:websocket', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}WebSocket Integration Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('WebSocket integration tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function parseTestOutput(output) {
const lines = output.split('\n');
let total = 0, passed = 0, failed = 0;
lines.forEach(line => {
const totalMatch = line.match(/Tests:\s+(\d+)/);
const passedMatch = line.match(/Passed:\s+(\d+)/);
const failedMatch = line.match(/Failed:\s+(\d+)/);
if (totalMatch) total = parseInt(totalMatch[1]);
if (passedMatch) passed = parseInt(passedMatch[1]);
if (failedMatch) failed = parseInt(failedMatch[1]);
});
return { total, passed, failed };
}
function runE2ETests() {
logStep(3, 'Running End-to-End Tests');
try {
const testResults = {
userFlow: runUserFlowTests(),
businessFlow: runBusinessFlowTests(),
adminFlow: runAdminFlowTests()
};
const totalTests = Object.values(testResults).reduce((sum, result) => sum + result.total, 0);
const passedTests = Object.values(testResults).reduce((sum, result) => sum + result.passed, 0);
const failedTests = Object.values(testResults).reduce((sum, result) => sum + result.failed, 0);
console.log(`\n${COLORS.cyan}E2E Tests Summary:${COLORS.reset}`);
console.log(` Total: ${totalTests}`);
console.log(` ${COLORS.green}Passed: ${passedTests}${COLORS.reset}`);
console.log(` ${COLORS.red}Failed: ${failedTests}${COLORS.reset}`);
console.log(` Success Rate: ${((passedTests / totalTests) * 100).toFixed(2)}%`);
return failedTests === 0;
} catch (error) {
logError(`E2E tests failed: ${error.message}`);
return false;
}
}
function runUserFlowTests() {
try {
const output = execSync('cd dashboard && npm run test:e2e:user', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}User Flow Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('User flow tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runBusinessFlowTests() {
try {
const output = execSync('cd dashboard && npm run test:e2e:business', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Business Flow Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Business flow tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runAdminFlowTests() {
try {
const output = execSync('cd dashboard && npm run test:e2e:admin', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Admin Flow Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Admin flow tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runPerformanceTests() {
logStep(4, 'Running Performance Tests');
try {
const testResults = {
load: runLoadTests(),
stress: runStressTests(),
endurance: runEnduranceTests()
};
const totalTests = Object.values(testResults).reduce((sum, result) => sum + result.total, 0);
const passedTests = Object.values(testResults).reduce((sum, result) => sum + result.passed, 0);
const failedTests = Object.values(testResults).reduce((sum, result) => sum + result.failed, 0);
console.log(`\n${COLORS.cyan}Performance Tests Summary:${COLORS.reset}`);
console.log(` Total: ${totalTests}`);
console.log(` ${COLORS.green}Passed: ${passedTests}${COLORS.reset}`);
console.log(` ${COLORS.red}Failed: ${failedTests}${COLORS.reset}`);
console.log(` Success Rate: ${((passedTests / totalTests) * 100).toFixed(2)}%`);
return failedTests === 0;
} catch (error) {
logError(`Performance tests failed: ${error.message}`);
return false;
}
}
function runLoadTests() {
try {
const output = execSync('cd server && npm run test:load', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Load Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Load tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runStressTests() {
try {
const output = execSync('cd server && npm run test:stress', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Stress Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Stress tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runEnduranceTests() {
try {
const output = execSync('cd server && npm run test:endurance', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Endurance Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Endurance tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runSecurityTests() {
logStep(5, 'Running Security Tests');
try {
const testResults = {
vulnerability: runVulnerabilityTests(),
penetration: runPenetrationTests(),
compliance: runComplianceTests()
};
const totalTests = Object.values(testResults).reduce((sum, result) => sum + result.total, 0);
const passedTests = Object.values(testResults).reduce((sum, result) => sum + result.passed, 0);
const failedTests = Object.values(testResults).reduce((sum, result) => sum + result.failed, 0);
console.log(`\n${COLORS.cyan}Security Tests Summary:${COLORS.reset}`);
console.log(` Total: ${totalTests}`);
console.log(` ${COLORS.green}Passed: ${passedTests}${COLORS.reset}`);
console.log(` ${COLORS.red}Failed: ${failedTests}${COLORS.reset}`);
console.log(` Success Rate: ${((passedTests / totalTests) * 100).toFixed(2)}%`);
return failedTests === 0;
} catch (error) {
logError(`Security tests failed: ${error.message}`);
return false;
}
}
function runVulnerabilityTests() {
try {
const output = execSync('npm audit --json', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = JSON.parse(output);
const vulnerabilities = results.vulnerabilities || {};
const total = Object.values(vulnerabilities).reduce((sum, vulns) => sum + vulns.length, 0);
const critical = vulnerabilities.critical ? vulnerabilities.critical.length : 0;
const high = vulnerabilities.high ? vulnerabilities.high.length : 0;
const moderate = vulnerabilities.moderate ? vulnerabilities.moderate.length : 0;
const low = vulnerabilities.low ? vulnerabilities.low.length : 0;
const passed = total - (critical + high);
const failed = critical + high;
console.log(`\n${COLORS.cyan}Vulnerability Tests:${COLORS.reset}`);
console.log(` Critical: ${critical}, High: ${high}, Moderate: ${moderate}, Low: ${low}`);
console.log(` Total: ${total}, Passed: ${passed}, Failed: ${failed}`);
return { total, passed, failed };
} catch (error) {
logError('Vulnerability tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runPenetrationTests() {
try {
const output = execSync('cd server && npm run test:security', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Penetration Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Penetration tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runComplianceTests() {
try {
const output = execSync('cd server && npm run test:compliance', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Compliance Tests:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Compliance tests failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runCodeQualityChecks() {
logStep(6, 'Running Code Quality Checks');
try {
const checkResults = {
linting: runLintingChecks(),
formatting: runFormattingChecks(),
complexity: runComplexityChecks(),
duplication: runDuplicationChecks()
};
const totalChecks = Object.values(checkResults).reduce((sum, result) => sum + result.total, 0);
const passedChecks = Object.values(checkResults).reduce((sum, result) => sum + result.passed, 0);
const failedChecks = Object.values(checkResults).reduce((sum, result) => sum + result.failed, 0);
console.log(`\n${COLORS.cyan}Code Quality Checks Summary:${COLORS.reset}`);
console.log(` Total: ${totalChecks}`);
console.log(` ${COLORS.green}Passed: ${passedChecks}${COLORS.reset}`);
console.log(` ${COLORS.red}Failed: ${failedChecks}${COLORS.reset}`);
console.log(` Success Rate: ${((passedChecks / totalChecks) * 100).toFixed(2)}%`);
return failedChecks === 0;
} catch (error) {
logError(`Code quality checks failed: ${error.message}`);
return false;
}
}
function runLintingChecks() {
try {
const output = execSync('npm run lint -- --format json', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = JSON.parse(output);
const total = results.length;
const passed = results.filter(r => r.errorCount === 0 && r.warningCount === 0).length;
const failed = total - passed;
console.log(`\n${COLORS.cyan}Linting Checks:${COLORS.reset}`);
console.log(` Total: ${total}, Passed: ${passed}, Failed: ${failed}`);
return { total, passed, failed };
} catch (error) {
logError('Linting checks failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runFormattingChecks() {
try {
const output = execSync('npm run format:check', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Formatting Checks:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Formatting checks failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runComplexityChecks() {
try {
const output = execSync('npm run complexity:check', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Complexity Checks:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Complexity checks failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function runDuplicationChecks() {
try {
const output = execSync('npm run duplication:check', {
encoding: 'utf8',
stdio: 'pipe'
});
const results = parseTestOutput(output);
console.log(`\n${COLORS.cyan}Duplication Checks:${COLORS.reset}`);
console.log(` Total: ${results.total}, Passed: ${results.passed}, Failed: ${results.failed}`);
return results;
} catch (error) {
logError('Duplication checks failed');
return { total: 0, passed: 0, failed: 1 };
}
}
function generateTestReport() {
logStep(7, 'Generating Test Report');
const report = {
timestamp: new Date().toISOString(),
project: {
name: 'Crawlful Hub',
version: '1.0.0'
},
summary: {
totalTests: 0,
passedTests: 0,
failedTests: 0,
skippedTests: 0,
successRate: 0,
duration: 0
},
categories: {
unitTests: {
total: 0,
passed: 0,
failed: 0,
coverage: 0
},
integrationTests: {
total: 0,
passed: 0,
failed: 0
},
e2eTests: {
total: 0,
passed: 0,
failed: 0
},
performanceTests: {
total: 0,
passed: 0,
failed: 0
},
securityTests: {
total: 0,
passed: 0,
failed: 0
},
codeQualityChecks: {
total: 0,
passed: 0,
failed: 0
}
},
recommendations: [
'Fix failing tests before deployment',
'Improve test coverage for critical paths',
'Add more integration tests',
'Implement automated security scanning',
'Set up continuous testing pipeline'
],
nextSteps: [
'Review test failures',
'Fix critical issues',
'Re-run failed tests',
'Update documentation',
'Prepare for deployment'
]
};
const reportPath = path.join(process.cwd(), 'test-report.json');
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
logSuccess(`Test report generated: ${reportPath}`);
return true;
}
function main() {
console.log(`${COLORS.bright}${COLORS.blue}
╔══════════════════════════════════════════════════════════╗
║ ║
║ CRAWLFUL HUB - COMPREHENSIVE TEST SUITE ║
║ ║
║ Complete Quality Assurance & Testing ║
║ ║
╚══════════════════════════════════════════════════════════╝
${COLORS.reset}`);
logStep('START', 'Starting comprehensive testing...\n');
const results = {
unitTests: runUnitTests(),
integrationTests: runIntegrationTests(),
e2eTests: runE2ETests(),
performanceTests: runPerformanceTests(),
securityTests: runSecurityTests(),
codeQualityChecks: runCodeQualityChecks(),
reportGeneration: generateTestReport()
};
console.log(`\n${COLORS.bright}${COLORS.blue}
══════════════════════════════════════════════════════════
TESTING COMPLETE
══════════════════════════════════════════════════════════
${COLORS.reset}`);
const successCount = Object.values(results).filter(Boolean).length;
const totalCount = Object.keys(results).length;
console.log(`\n${COLORS.cyan}Summary:${COLORS.reset}`);
console.log(` ${COLORS.green}${COLORS.reset} Successful: ${successCount}/${totalCount}`);
console.log(` ${COLORS.red}${COLORS.reset} Failed: ${totalCount - successCount}/${totalCount}`);
if (successCount === totalCount) {
logSuccess('🚀 All tests completed successfully!');
console.log(`\n${COLORS.yellow}Next Steps:${COLORS.reset}`);
console.log(' 1. Review test-report.json');
console.log(' 2. Check test coverage reports');
console.log(' 3. Review performance metrics');
console.log(' 4. Address any warnings');
console.log(' 5. Prepare for deployment');
} else {
logWarning('Some tests failed. Please review the errors above.');
console.log(`\n${COLORS.yellow}Action Items:${COLORS.reset}`);
console.log(' 1. Review test failures');
console.log(' 2. Fix critical issues');
console.log(' 3. Re-run failed tests');
console.log(' 4. Update documentation');
}
process.exit(successCount === totalCount ? 0 : 1);
}
if (require.main === module) {
main();
}
module.exports = {
runUnitTests,
runIntegrationTests,
runE2ETests,
runPerformanceTests,
runSecurityTests,
runCodeQualityChecks,
generateTestReport
};