full-voice-integration-test.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. #!/usr/bin/env node
  2. /**
  3. * 完整语音集成测试报告
  4. * 验证培训包的语音克隆能力是否完整可用
  5. */
  6. const path = require('path');
  7. const fs = require('fs');
  8. const https = require('https');
  9. const TRAINING_DIR = path.resolve('D:', 'qiwei-training');
  10. console.log('🧪 完整语音集成测试报告');
  11. console.log('='.repeat(70));
  12. console.log(`测试目标: ${TRAINING_DIR}`);
  13. console.log(`测试时间: ${new Date().toLocaleString('zh-CN')}\n`);
  14. let passCount = 0;
  15. let failCount = 0;
  16. function reportTest(name, passed, details = '') {
  17. if (passed) {
  18. console.log(`✅ [通过] ${name}`);
  19. if (details) console.log(` ${details}`);
  20. passCount++;
  21. } else {
  22. console.log(`❌ [失败] ${name}`);
  23. if (details) console.log(` ${details}`);
  24. failCount++;
  25. }
  26. }
  27. // ============================================================
  28. // 测试 1: 培训包结构
  29. // ============================================================
  30. console.log('【第 1 组】培训包结构验证\n');
  31. const exePath = path.join(TRAINING_DIR, 'qiwei-workbench.exe');
  32. const exeExists = fs.existsSync(exePath);
  33. const exeSize = exeExists ? (fs.statSync(exePath).size / (1024 * 1024)).toFixed(1) : 0;
  34. reportTest(
  35. 'exe 文件存在且大小合理',
  36. exeExists && parseFloat(exeSize) > 90,
  37. `大小: ${exeSize} MB`
  38. );
  39. const envPath = path.join(TRAINING_DIR, '.env.local');
  40. const envExists = fs.existsSync(envPath);
  41. reportTest('配置文件存在', envExists, envPath);
  42. const readmePath = path.join(TRAINING_DIR, 'README.md');
  43. const readmeExists = fs.existsSync(readmePath);
  44. reportTest('用户手册存在', readmeExists, readmePath);
  45. // ============================================================
  46. // 测试 2: Token 配置
  47. // ============================================================
  48. console.log('\n【第 2 组】认证配置验证\n');
  49. if (envExists) {
  50. const envContent = fs.readFileSync(envPath, 'utf8');
  51. const tokenMatch = envContent.match(/^QIWEI_AUTH_TOKEN=(.+)$/m);
  52. const hasToken = tokenMatch && tokenMatch[1].trim().length > 0;
  53. reportTest(
  54. 'Token 已配置',
  55. hasToken,
  56. hasToken ? '已检测到凭据(内容已隐藏)' : '未找到有效 Token'
  57. );
  58. if (hasToken) {
  59. const token = tokenMatch[1].trim();
  60. const validFormat = token.startsWith('sk-') || token.startsWith('r:');
  61. reportTest('Token 格式正确', validFormat, validFormat ? '格式符合要求(内容已隐藏)' : '格式不符合要求');
  62. }
  63. }
  64. // ============================================================
  65. // 测试 3: README 语音文档
  66. // ============================================================
  67. console.log('\n【第 3 组】用户文档验证\n');
  68. if (readmeExists) {
  69. const readme = fs.readFileSync(readmePath, 'utf8');
  70. const hasVoiceSection = readme.includes('## 语音克隆功能');
  71. reportTest('包含语音克隆章节', hasVoiceSection);
  72. const hasReferenceAudio = readme.includes('录制参考音频') && readme.includes('5-30 秒');
  73. reportTest('说明参考音频要求', hasReferenceAudio);
  74. const hasBilling = readme.includes('3元/万字符') || readme.includes('计费说明');
  75. reportTest('说明计费信息', hasBilling);
  76. const hasSILK = readme.includes('SILK');
  77. reportTest('说明 SILK 编码', hasSILK);
  78. }
  79. // ============================================================
  80. // 测试 4: 源码模块完整性
  81. // ============================================================
  82. console.log('\n【第 4 组】源码模块验证\n');
  83. const voiceServicePath = path.resolve(__dirname, '..', 'mcp/src/core/voice-clone-service.js');
  84. const voiceServiceExists = fs.existsSync(voiceServicePath);
  85. reportTest('语音服务模块存在', voiceServiceExists, voiceServicePath);
  86. if (voiceServiceExists) {
  87. const voiceService = fs.readFileSync(voiceServicePath, 'utf8');
  88. const hasClass = voiceService.includes('class VoiceCloneService');
  89. reportTest('VoiceCloneService 类定义', hasClass);
  90. const hasSynthesize = voiceService.includes('async synthesize');
  91. reportTest('synthesize 方法定义', hasSynthesize);
  92. const hasEncodeSilk = voiceService.includes('async encodeSilk');
  93. reportTest('encodeSilk 方法定义', hasEncodeSilk);
  94. const hasEndpoint = voiceService.includes('server.fmode.cn/api/voice/indextts2');
  95. reportTest('Fmode 后端端点配置', hasEndpoint);
  96. const hasWxVoice = voiceService.includes('@binsee/wx-voice');
  97. reportTest('wx-voice 依赖引用', hasWxVoice);
  98. const hasFfmpeg = voiceService.includes('ffmpeg');
  99. reportTest('ffmpeg 依赖引用', hasFfmpeg);
  100. }
  101. // ============================================================
  102. // 测试 5: package.json 依赖声明
  103. // ============================================================
  104. console.log('\n【第 5 组】依赖声明验证\n');
  105. const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
  106. const packageJson = require(packageJsonPath);
  107. const allDeps = {
  108. ...packageJson.dependencies,
  109. ...packageJson.optionalDependencies
  110. };
  111. const voiceDeps = [
  112. '@ffmpeg-installer/ffmpeg',
  113. '@ffprobe-installer/ffprobe',
  114. '@binsee/wx-voice'
  115. ];
  116. voiceDeps.forEach(dep => {
  117. const declared = !!allDeps[dep];
  118. reportTest(`依赖 ${dep}`, declared, declared ? `版本: ${allDeps[dep]}` : '未声明');
  119. });
  120. // ============================================================
  121. // 测试 6: 运行时加载
  122. // ============================================================
  123. console.log('\n【第 6 组】运行时加载验证\n');
  124. try {
  125. const { VoiceCloneService } = require('../mcp/src/core/voice-clone-service.js');
  126. reportTest('VoiceCloneService 类加载', true);
  127. const mockConfig = {
  128. config: { endpoint: 'https://server.fmode.cn/api/voice/indextts2', authToken: 'test' },
  129. qiwei: { uid: 'test', guid: 'test' }
  130. };
  131. const service = new VoiceCloneService(mockConfig);
  132. reportTest('VoiceCloneService 实例化', true, `端点: ${service.config.endpoint}`);
  133. } catch (err) {
  134. reportTest('VoiceCloneService 加载/实例化', false, err.message);
  135. }
  136. try {
  137. const ffmpeg = require('@ffmpeg-installer/ffmpeg');
  138. const ffmpegExists = fs.existsSync(ffmpeg.path);
  139. reportTest('ffmpeg 二进制可访问', ffmpegExists, `路径: ${ffmpeg.path}`);
  140. } catch (err) {
  141. reportTest('ffmpeg 加载', false, err.message);
  142. }
  143. try {
  144. const ffprobe = require('@ffprobe-installer/ffprobe');
  145. const ffprobeExists = fs.existsSync(ffprobe.path);
  146. reportTest('ffprobe 二进制可访问', ffprobeExists, `路径: ${ffprobe.path}`);
  147. } catch (err) {
  148. reportTest('ffprobe 加载', false, err.message);
  149. }
  150. try {
  151. const WxVoice = require('@binsee/wx-voice');
  152. const hasEncode = typeof WxVoice.WxVoice === 'function';
  153. reportTest('wx-voice 模块可用', hasEncode);
  154. } catch (err) {
  155. reportTest('wx-voice 加载', false, err.message);
  156. }
  157. // ============================================================
  158. // 测试 7: exe 启动检查
  159. // ============================================================
  160. console.log('\n【第 7 组】exe 启动检查\n');
  161. const { execSync } = require('child_process');
  162. try {
  163. const helpOutput = execSync(
  164. `"${exePath}" --help`,
  165. { encoding: 'utf8', timeout: 5000 }
  166. );
  167. const canRun = helpOutput.includes('企微培训工作台');
  168. reportTest('exe 可以正常启动', canRun, '命令行参数解析正常');
  169. } catch (err) {
  170. reportTest('exe 启动测试', false, err.message);
  171. }
  172. // ============================================================
  173. // 汇总报告
  174. // ============================================================
  175. console.log('\n' + '='.repeat(70));
  176. console.log('📊 测试汇总');
  177. console.log('='.repeat(70));
  178. console.log(`✅ 通过: ${passCount} 项`);
  179. console.log(`❌ 失败: ${failCount} 项`);
  180. console.log(`📈 通过率: ${((passCount / (passCount + failCount)) * 100).toFixed(1)}%`);
  181. if (failCount === 0) {
  182. console.log('\n🎉 所有测试通过!培训包语音克隆功能完整可用。');
  183. console.log('\n📝 下一步建议:');
  184. console.log(' 1. 准备 5-30 秒清晰的参考录音(WAV/MP3 格式)');
  185. console.log(' 2. 启动工作台: D:\\qiwei-training\\qiwei-workbench.exe');
  186. console.log(' 3. 在工作台上传参考音频完成初始化');
  187. console.log(' 4. 输入测试文本并发送到测试联系人验证效果');
  188. console.log(' 5. 验证通过后可打包分发或部署到培训机');
  189. process.exit(0);
  190. } else {
  191. console.log('\n⚠️ 部分测试未通过,请检查上述失败项。');
  192. process.exit(1);
  193. }