#!/usr/bin/env node /** * 语音克隆培训包 - 冒烟测试 * * 测试场景: * 1. 检查培训包结构完整性 * 2. 模拟语音服务调用(不真实发送) * 3. 验证 Fmode 后端连通性 */ const path = require('path'); const fs = require('fs'); const ROOT = path.join(__dirname, '..'); const TRAINING_DIR = path.join(ROOT, 'dist/qiwei-training'); console.log('🧪 语音克隆培训包 - 冒烟测试\n'); // ============================================================ // 测试 1:培训包结构完整性 // ============================================================ function test1_PackageStructure() { console.log('【测试 1】检查培训包结构...'); const required = [ 'qiwei-workbench.exe', '.env.local', 'README.md', 'qiwei.runtime.config.mjs', 'web', 'knowledge', ]; const missing = required.filter(item => { const fullPath = path.join(TRAINING_DIR, item); return !fs.existsSync(fullPath); }); if (missing.length > 0) { console.error(` ❌ 缺少文件/目录: ${missing.join(', ')}`); return false; } const exeSize = (fs.statSync(path.join(TRAINING_DIR, 'qiwei-workbench.exe')).size / (1024 * 1024)).toFixed(1); console.log(` ✅ 结构完整,exe 大小: ${exeSize} MB`); return true; } // ============================================================ // 测试 2:README 语音说明 // ============================================================ function test2_READMEContent() { console.log('\n【测试 2】检查 README 语音说明...'); const readme = fs.readFileSync(path.join(TRAINING_DIR, 'README.md'), 'utf8'); const requiredSections = [ '## 语音克隆功能', '录制参考音频', '初始化声音档案', 'SILK 格式', 'Fmode 飞马余额', ]; const missing = requiredSections.filter(section => !readme.includes(section)); if (missing.length > 0) { console.error(` ❌ README 缺少说明: ${missing.join(', ')}`); return false; } console.log(' ✅ README 包含完整语音使用说明'); return true; } // ============================================================ // 测试 3:语音服务模块完整性 // ============================================================ function test3_VoiceServiceModule() { console.log('\n【测试 3】检查语音服务模块...'); const voiceService = path.join(ROOT, 'mcp/src/core/voice-clone-service.js'); if (!fs.existsSync(voiceService)) { console.error(' ❌ 语音服务模块不存在'); return false; } const content = fs.readFileSync(voiceService, 'utf8'); const criticalComponents = [ 'class VoiceCloneService', 'async synthesize', 'async encodeSilk', 'server.fmode.cn/api/voice/indextts2', '@binsee/wx-voice', 'ffmpeg', ]; const missing = criticalComponents.filter(comp => !content.includes(comp)); if (missing.length > 0) { console.error(` ❌ 语音服务缺少组件: ${missing.join(', ')}`); return false; } console.log(' ✅ 语音服务模块完整'); return true; } // ============================================================ // 测试 4:语音依赖声明 // ============================================================ function test4_VoiceDependencies() { console.log('\n【测试 4】检查语音依赖...'); const packageJson = require(path.join(ROOT, 'package.json')); const voiceDeps = [ '@ffmpeg-installer/ffmpeg', '@ffprobe-installer/ffprobe', '@binsee/wx-voice', ]; const allDeps = { ...packageJson.dependencies, ...packageJson.optionalDependencies, }; const missing = voiceDeps.filter(dep => !allDeps[dep]); if (missing.length > 0) { console.error(` ❌ package.json 缺少依赖: ${missing.join(', ')}`); return false; } console.log(' ✅ 语音依赖已声明'); voiceDeps.forEach(dep => { console.log(` - ${dep}: ${allDeps[dep]}`); }); return true; } // ============================================================ // 测试 5:MCP 工具注册 // ============================================================ function test5_MCPToolsRegistration() { console.log('\n【测试 5】检查 MCP 语音工具注册...'); const serverJs = path.join(ROOT, 'mcp/src/server.js'); if (!fs.existsSync(serverJs)) { console.error(' ❌ MCP server.js 不存在'); return false; } const content = fs.readFileSync(serverJs, 'utf8'); const voiceTools = [ 'qiwei_enroll_voice', 'qiwei_voice_profile_status', 'qiwei_send_cloned_voice', ]; const missing = voiceTools.filter(tool => !content.includes(tool)); if (missing.length > 0) { console.error(` ❌ 缺少 MCP 工具: ${missing.join(', ')}`); return false; } console.log(' ✅ MCP 语音工具已注册'); return true; } // ============================================================ // 测试 6:Fmode 后端连通性(可选) // ============================================================ async function test6_FmodeBackendConnectivity() { console.log('\n【测试 6】检查 Fmode 后端连通性...'); // 读取 .env.local 中的 token(如果存在) const envPath = path.join(TRAINING_DIR, '.env.local'); const envContent = fs.readFileSync(envPath, 'utf8'); const tokenMatch = envContent.match(/QIWEI_AUTH_TOKEN=(.+)/); if (!tokenMatch || !tokenMatch[1] || tokenMatch[1].trim() === '') { console.log(' ⚠️ 跳过(未配置 QIWEI_AUTH_TOKEN)'); return true; } const token = tokenMatch[1].trim(); try { const https = require('https'); const endpoint = 'https://server.fmode.cn/api/product/balance'; const result = await new Promise((resolve, reject) => { const req = https.get(endpoint, { headers: { 'Authorization': `Bearer ${token}`, }, }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { if (res.statusCode === 200) { resolve(JSON.parse(data)); } else { reject(new Error(`HTTP ${res.statusCode}: ${data}`)); } }); }); req.on('error', reject); req.setTimeout(5000, () => { req.destroy(); reject(new Error('连接超时')); }); }); console.log(' ✅ Fmode 后端连通'); console.log(` 余额: ${result.balance || 'N/A'} 元`); return true; } catch (err) { console.error(` ❌ Fmode 后端连接失败: ${err.message}`); return false; } } // ============================================================ // 主测试流程 // ============================================================ async function main() { const tests = [ { name: 'PackageStructure', fn: test1_PackageStructure, required: true }, { name: 'READMEContent', fn: test2_READMEContent, required: true }, { name: 'VoiceServiceModule', fn: test3_VoiceServiceModule, required: true }, { name: 'VoiceDependencies', fn: test4_VoiceDependencies, required: true }, { name: 'MCPToolsRegistration', fn: test5_MCPToolsRegistration, required: true }, { name: 'FmodeBackendConnectivity', fn: test6_FmodeBackendConnectivity, required: false }, ]; let passed = 0; let failed = 0; let skipped = 0; for (const test of tests) { try { const result = await test.fn(); if (result) { passed++; } else { failed++; if (test.required) { console.error(`\n❌ 必要测试失败: ${test.name}`); } } } catch (err) { console.error(` ❌ 测试异常: ${err.message}`); if (test.required) { failed++; } else { skipped++; } } } console.log('\n' + '='.repeat(60)); console.log(`测试完成: ${passed} 通过, ${failed} 失败, ${skipped} 跳过`); console.log('='.repeat(60)); if (failed > 0) { console.error('\n❌ 冒烟测试失败,请检查上述错误'); process.exit(1); } else { console.log('\n✅ 冒烟测试通过!培训包可以部署。'); console.log('\n下一步:'); console.log(' 1. 拷贝 dist/qiwei-training/ 到培训机'); console.log(' 2. 配置 .env.local 中的 QIWEI_AUTH_TOKEN'); console.log(' 3. 双击 qiwei-workbench.exe 启动'); console.log(' 4. 按 README 说明录制参考音频并测试语音发送'); } } main().catch(err => { console.error('测试执行失败:', err.message); process.exit(1); });