#!/usr/bin/env node /** * 完整语音集成测试报告 * 验证培训包的语音克隆能力是否完整可用 */ const path = require('path'); const fs = require('fs'); const https = require('https'); const TRAINING_DIR = path.resolve('D:', 'qiwei-training'); console.log('🧪 完整语音集成测试报告'); console.log('='.repeat(70)); console.log(`测试目标: ${TRAINING_DIR}`); console.log(`测试时间: ${new Date().toLocaleString('zh-CN')}\n`); let passCount = 0; let failCount = 0; function reportTest(name, passed, details = '') { if (passed) { console.log(`✅ [通过] ${name}`); if (details) console.log(` ${details}`); passCount++; } else { console.log(`❌ [失败] ${name}`); if (details) console.log(` ${details}`); failCount++; } } // ============================================================ // 测试 1: 培训包结构 // ============================================================ console.log('【第 1 组】培训包结构验证\n'); const exePath = path.join(TRAINING_DIR, 'qiwei-workbench.exe'); const exeExists = fs.existsSync(exePath); const exeSize = exeExists ? (fs.statSync(exePath).size / (1024 * 1024)).toFixed(1) : 0; reportTest( 'exe 文件存在且大小合理', exeExists && parseFloat(exeSize) > 90, `大小: ${exeSize} MB` ); const envPath = path.join(TRAINING_DIR, '.env.local'); const envExists = fs.existsSync(envPath); reportTest('配置文件存在', envExists, envPath); const readmePath = path.join(TRAINING_DIR, 'README.md'); const readmeExists = fs.existsSync(readmePath); reportTest('用户手册存在', readmeExists, readmePath); // ============================================================ // 测试 2: Token 配置 // ============================================================ console.log('\n【第 2 组】认证配置验证\n'); if (envExists) { const envContent = fs.readFileSync(envPath, 'utf8'); const tokenMatch = envContent.match(/^QIWEI_AUTH_TOKEN=(.+)$/m); const hasToken = tokenMatch && tokenMatch[1].trim().length > 0; reportTest( 'Token 已配置', hasToken, hasToken ? '已检测到凭据(内容已隐藏)' : '未找到有效 Token' ); if (hasToken) { const token = tokenMatch[1].trim(); const validFormat = token.startsWith('sk-') || token.startsWith('r:'); reportTest('Token 格式正确', validFormat, validFormat ? '格式符合要求(内容已隐藏)' : '格式不符合要求'); } } // ============================================================ // 测试 3: README 语音文档 // ============================================================ console.log('\n【第 3 组】用户文档验证\n'); if (readmeExists) { const readme = fs.readFileSync(readmePath, 'utf8'); const hasVoiceSection = readme.includes('## 语音克隆功能'); reportTest('包含语音克隆章节', hasVoiceSection); const hasReferenceAudio = readme.includes('录制参考音频') && readme.includes('5-30 秒'); reportTest('说明参考音频要求', hasReferenceAudio); const hasBilling = readme.includes('3元/万字符') || readme.includes('计费说明'); reportTest('说明计费信息', hasBilling); const hasSILK = readme.includes('SILK'); reportTest('说明 SILK 编码', hasSILK); } // ============================================================ // 测试 4: 源码模块完整性 // ============================================================ console.log('\n【第 4 组】源码模块验证\n'); const voiceServicePath = path.resolve(__dirname, '..', 'mcp/src/core/voice-clone-service.js'); const voiceServiceExists = fs.existsSync(voiceServicePath); reportTest('语音服务模块存在', voiceServiceExists, voiceServicePath); if (voiceServiceExists) { const voiceService = fs.readFileSync(voiceServicePath, 'utf8'); const hasClass = voiceService.includes('class VoiceCloneService'); reportTest('VoiceCloneService 类定义', hasClass); const hasSynthesize = voiceService.includes('async synthesize'); reportTest('synthesize 方法定义', hasSynthesize); const hasEncodeSilk = voiceService.includes('async encodeSilk'); reportTest('encodeSilk 方法定义', hasEncodeSilk); const hasEndpoint = voiceService.includes('server.fmode.cn/api/voice/indextts2'); reportTest('Fmode 后端端点配置', hasEndpoint); const hasWxVoice = voiceService.includes('@binsee/wx-voice'); reportTest('wx-voice 依赖引用', hasWxVoice); const hasFfmpeg = voiceService.includes('ffmpeg'); reportTest('ffmpeg 依赖引用', hasFfmpeg); } // ============================================================ // 测试 5: package.json 依赖声明 // ============================================================ console.log('\n【第 5 组】依赖声明验证\n'); const packageJsonPath = path.resolve(__dirname, '..', 'package.json'); const packageJson = require(packageJsonPath); const allDeps = { ...packageJson.dependencies, ...packageJson.optionalDependencies }; const voiceDeps = [ '@ffmpeg-installer/ffmpeg', '@ffprobe-installer/ffprobe', '@binsee/wx-voice' ]; voiceDeps.forEach(dep => { const declared = !!allDeps[dep]; reportTest(`依赖 ${dep}`, declared, declared ? `版本: ${allDeps[dep]}` : '未声明'); }); // ============================================================ // 测试 6: 运行时加载 // ============================================================ console.log('\n【第 6 组】运行时加载验证\n'); try { const { VoiceCloneService } = require('../mcp/src/core/voice-clone-service.js'); reportTest('VoiceCloneService 类加载', true); const mockConfig = { config: { endpoint: 'https://server.fmode.cn/api/voice/indextts2', authToken: 'test' }, qiwei: { uid: 'test', guid: 'test' } }; const service = new VoiceCloneService(mockConfig); reportTest('VoiceCloneService 实例化', true, `端点: ${service.config.endpoint}`); } catch (err) { reportTest('VoiceCloneService 加载/实例化', false, err.message); } try { const ffmpeg = require('@ffmpeg-installer/ffmpeg'); const ffmpegExists = fs.existsSync(ffmpeg.path); reportTest('ffmpeg 二进制可访问', ffmpegExists, `路径: ${ffmpeg.path}`); } catch (err) { reportTest('ffmpeg 加载', false, err.message); } try { const ffprobe = require('@ffprobe-installer/ffprobe'); const ffprobeExists = fs.existsSync(ffprobe.path); reportTest('ffprobe 二进制可访问', ffprobeExists, `路径: ${ffprobe.path}`); } catch (err) { reportTest('ffprobe 加载', false, err.message); } try { const WxVoice = require('@binsee/wx-voice'); const hasEncode = typeof WxVoice.WxVoice === 'function'; reportTest('wx-voice 模块可用', hasEncode); } catch (err) { reportTest('wx-voice 加载', false, err.message); } // ============================================================ // 测试 7: exe 启动检查 // ============================================================ console.log('\n【第 7 组】exe 启动检查\n'); const { execSync } = require('child_process'); try { const helpOutput = execSync( `"${exePath}" --help`, { encoding: 'utf8', timeout: 5000 } ); const canRun = helpOutput.includes('企微培训工作台'); reportTest('exe 可以正常启动', canRun, '命令行参数解析正常'); } catch (err) { reportTest('exe 启动测试', false, err.message); } // ============================================================ // 汇总报告 // ============================================================ console.log('\n' + '='.repeat(70)); console.log('📊 测试汇总'); console.log('='.repeat(70)); console.log(`✅ 通过: ${passCount} 项`); console.log(`❌ 失败: ${failCount} 项`); console.log(`📈 通过率: ${((passCount / (passCount + failCount)) * 100).toFixed(1)}%`); if (failCount === 0) { console.log('\n🎉 所有测试通过!培训包语音克隆功能完整可用。'); console.log('\n📝 下一步建议:'); console.log(' 1. 准备 5-30 秒清晰的参考录音(WAV/MP3 格式)'); console.log(' 2. 启动工作台: D:\\qiwei-training\\qiwei-workbench.exe'); console.log(' 3. 在工作台上传参考音频完成初始化'); console.log(' 4. 输入测试文本并发送到测试联系人验证效果'); console.log(' 5. 验证通过后可打包分发或部署到培训机'); process.exit(0); } else { console.log('\n⚠️ 部分测试未通过,请检查上述失败项。'); process.exit(1); }