| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- #!/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);
- });
|