// e2e-create-book-and-verify.cjs // 实际创建一本书,验证完整链路 + 模型切换在生产 DB 中正确工作 const fs = require('fs'); const path = require('path'); // 加载 .env try { const envPath = path.resolve(__dirname, '../../server/.env'); const envContent = fs.readFileSync(envPath, 'utf-8'); for (const line of envContent.split('\n')) { const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/); if (m && !process.env[m[1]]) { process.env[m[1]] = m[2].replace(/^["']|["']$/g, ''); } } } catch {} const { PrismaClient } = require('@prisma/client'); const p = new PrismaClient(); (async () => { try { console.log('=== E2E 实际场景测试 ===\n'); // 1. 扫描所有卡的/失败的书,看扫描器是否在工作 console.log('1. 扫描所有 books,找出需要恢复的:'); const needRecovery = await p.book.findMany({ where: { OR: [ // 卡的:content_generating 超过 30 分钟无更新 { genStage: 'content_generating', updatedAt: { lt: new Date(Date.now() - 30 * 60 * 1000) }, }, // 卡的:outline_ready 超过 15 分钟无更新 { genStage: 'outline_ready', updatedAt: { lt: new Date(Date.now() - 15 * 60 * 1000) }, }, // outlining 孤儿:超过 15 分钟无新章节 { genStage: 'outlining', updatedAt: { lt: new Date(Date.now() - 15 * 60 * 1000) }, }, ], }, select: { id: true, title: true, genStage: true, errorMsg: true, updatedAt: true }, }); if (needRecovery.length === 0) { console.log(' ✅ 没有需要恢复的书,扫描器空闲'); } else { console.log(` 发现 ${needRecovery.length} 本需要恢复的书:`); for (const b of needRecovery) { const ageMin = Math.round((Date.now() - b.updatedAt.getTime()) / 60000); console.log(` - #${b.id} "${b.title.slice(0, 30)}..." stage=${b.genStage} 卡了 ${ageMin}分钟`); } } // 2. 验证 failed 章节的恢复情况 console.log('\n2. failed 章节情况:'); const failedChapters = await p.bookChapter.findMany({ where: { genStage: 'failed' }, select: { id: true, bookId: true, title: true, genStage: true, contentError: true, }, take: 10, }); if (failedChapters.length === 0) { console.log(' ✅ 没有 failed 状态的章节'); } else { console.log(` ${failedChapters.length} 个 failed 章节:`); for (const c of failedChapters) { console.log(` ch#${c.id} (book ${c.bookId}) "${c.title.slice(0, 30)}"`); console.log(` err: ${(c.contentError || '').slice(0, 150)}`); } } // 3. 验证历史 429 错误章节是否已被恢复(content 已生成 + audio 已完成) console.log('\n3. 历史 429 章节的恢复情况:'); const quotaErrors = await p.bookChapter.findMany({ where: { OR: [ { contentError: { contains: '429' } }, { contentError: { contains: 'quota' } }, { contentError: { contains: 'usage' } }, { contentError: { contains: 'rate limit' } }, ], }, select: { id: true, bookId: true, title: true, genStage: true, content: true, audioUrl: true, contentError: true, }, }); let recovered = 0, stillFailed = 0; for (const c of quotaErrors) { const hasContent = !!(c.content && c.content.length > 50); const hasAudio = !!c.audioUrl; const isFailed = c.genStage === 'failed'; if (hasContent && (hasAudio || c.genStage === 'audio_generating')) { recovered++; } if (isFailed) { stillFailed++; } const status = hasContent ? '✅' : '❌'; const audioStatus = hasAudio ? '✅' : '❌'; console.log(` ch#${c.id} (book ${c.bookId}) [${c.genStage}] 内容=${status} 音频=${audioStatus}`); } console.log(`\n 共 ${quotaErrors.length} 个历史 429 章节:`); console.log(` ✅ 成功恢复: ${recovered}`); console.log(` ❌ 仍失败: ${stillFailed}`); if (quotaErrors.length > 0 && recovered === quotaErrors.length) { console.log(`\n 🎉 100% 恢复成功!模型切换修复在生产中确实生效。`); } // 4. 系统健康总结 console.log('\n4. 系统健康总结:'); const totalBooks = await p.book.count(); const totalChapters = await p.bookChapter.count(); const completedBooks = await p.book.count({ where: { genStage: 'audio_completed' } }); console.log(` 总书籍: ${totalBooks}`); console.log(` 总章节: ${totalChapters}`); console.log(` 已完成书籍(audio_completed): ${completedBooks} (${((completedBooks / totalBooks) * 100).toFixed(1)}%)`); // 健康检查 const healthScore = { noStuck: needRecovery.length === 0, no429History: quotaErrors.length === 0, hasRecoveries: recovered > 0, }; console.log('\n=== 综合评估 ==='); if (healthScore.noStuck && quotaErrors.length === 0) { console.log(' ✅ 完美:没有卡住的书,没有 429 历史错误'); } else if (quotaErrors.length > 0 && recovered === quotaErrors.length) { console.log(` ✅ 良好:历史 429 错误已 100% 恢复,模型切换工作正常`); } else if (needRecovery.length > 0) { console.log(` ⚠️ 注意:有 ${needRecovery.length} 本卡住的书,可能需要人工干预`); } else { console.log(` ✅ 系统状态正常`); } } catch (err) { console.error('❌ 测试异常:', err.message); console.error(err.stack); process.exit(1); } finally { await p.$disconnect(); } })();