Files
AIclinicalresearch/backend/test-review-api.js
HaHafeng 88cc049fb3 feat(asl): Complete Day 5 - Fulltext Screening Backend API Development
- Implement 5 core API endpoints (create task, get progress, get results, update decision, export Excel)
- Add FulltextScreeningController with Zod validation (652 lines)
- Implement ExcelExporter service with 4-sheet report generation (352 lines)
- Register routes under /api/v1/asl/fulltext-screening
- Create 31 REST Client test cases
- Add automated integration test script
- Fix PDF extraction fallback mechanism in LLM12FieldsService
- Update API design documentation to v3.0
- Update development plan to v1.2
- Create Day 5 development record
- Clean up temporary test files
2025-11-23 10:52:07 +08:00

422 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 稿件审查API测试脚本
* 测试5个API端点的功能
*/
import axios from 'axios';
import FormData from 'form-data';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const API_BASE_URL = 'http://localhost:3001/api/v1';
const TEST_FILE_PATH = path.join(__dirname, '../docs/稿约规范性评估标准.txt'); // 使用现有文本文件作为测试
// 颜色输出
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
};
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
function separator() {
console.log('\n' + '='.repeat(80) + '\n');
}
// 延迟函数
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ==================== 测试函数 ====================
/**
* 测试1: 上传稿件
*/
async function testUploadManuscript() {
log('📤 测试1: 上传稿件 (POST /review/upload)', 'cyan');
try {
// 检查测试文件是否存在
if (!fs.existsSync(TEST_FILE_PATH)) {
log(`❌ 测试文件不存在: ${TEST_FILE_PATH}`, 'red');
log('💡 提示请准备一个Word文档.doc或.docx作为测试文件', 'yellow');
return null;
}
const formData = new FormData();
formData.append('file', fs.createReadStream(TEST_FILE_PATH));
formData.append('modelType', 'deepseek-v3');
log(`📄 测试文件: ${path.basename(TEST_FILE_PATH)}`, 'blue');
log(`🤖 使用模型: deepseek-v3`, 'blue');
const response = await axios.post(
`${API_BASE_URL}/review/upload`,
formData,
{
headers: formData.getHeaders(),
timeout: 30000,
}
);
if (response.data.success) {
log('✅ 上传成功!', 'green');
console.log('返回数据:', JSON.stringify(response.data, null, 2));
return response.data.data.taskId;
} else {
log('❌ 上传失败', 'red');
console.log('错误信息:', response.data.message);
return null;
}
} catch (error) {
log('❌ 请求失败', 'red');
if (error.response) {
console.log('状态码:', error.response.status);
console.log('错误信息:', error.response.data);
} else {
console.log('错误:', error.message);
}
return null;
}
}
/**
* 测试2: 查询任务状态
*/
async function testGetTaskStatus(taskId) {
log('🔍 测试2: 查询任务状态 (GET /review/tasks/:taskId)', 'cyan');
try {
log(`📋 任务ID: ${taskId}`, 'blue');
const response = await axios.get(`${API_BASE_URL}/review/tasks/${taskId}`, {
timeout: 10000,
});
if (response.data.success) {
log('✅ 查询成功!', 'green');
console.log('任务状态:', JSON.stringify(response.data.data, null, 2));
return response.data.data;
} else {
log('❌ 查询失败', 'red');
console.log('错误信息:', response.data.message);
return null;
}
} catch (error) {
log('❌ 请求失败', 'red');
if (error.response) {
console.log('状态码:', error.response.status);
console.log('错误信息:', error.response.data);
} else {
console.log('错误:', error.message);
}
return null;
}
}
/**
* 测试3: 轮询任务直到完成
*/
async function pollTaskUntilComplete(taskId, maxAttempts = 60) {
log('⏳ 测试3: 轮询任务状态直到完成', 'cyan');
for (let i = 0; i < maxAttempts; i++) {
const task = await testGetTaskStatus(taskId);
if (!task) {
log('❌ 查询任务失败,停止轮询', 'red');
return null;
}
log(`📊 当前状态: ${task.status}`, 'yellow');
if (task.status === 'completed') {
log('✅ 任务已完成!', 'green');
return task;
}
if (task.status === 'failed') {
log('❌ 任务失败', 'red');
console.log('错误信息:', task.errorMessage);
return null;
}
log(`⏱️ 等待5秒后重试... (${i + 1}/${maxAttempts})`, 'blue');
await delay(5000);
}
log('⚠️ 超过最大等待时间,任务仍未完成', 'yellow');
return null;
}
/**
* 测试4: 获取审查报告
*/
async function testGetReport(taskId) {
log('📊 测试4: 获取审查报告 (GET /review/tasks/:taskId/report)', 'cyan');
try {
log(`📋 任务ID: ${taskId}`, 'blue');
const response = await axios.get(`${API_BASE_URL}/review/tasks/${taskId}/report`, {
timeout: 10000,
});
if (response.data.success) {
log('✅ 获取报告成功!', 'green');
console.log('\n📄 完整报告:');
console.log(JSON.stringify(response.data.data, null, 2));
// 显示关键指标
const report = response.data.data;
separator();
log('📈 评估结果摘要:', 'cyan');
log(`总分: ${report.overallScore?.toFixed(1) || 'N/A'}`, 'green');
log(`稿约规范性评分: ${report.editorialReview?.overall_score || 'N/A'}`, 'blue');
log(`方法学评分: ${report.methodologyReview?.overall_score || 'N/A'}`, 'blue');
log(`字数: ${report.wordCount || 'N/A'}`, 'blue');
log(`耗时: ${report.durationSeconds || 'N/A'}`, 'blue');
separator();
return response.data.data;
} else {
log('❌ 获取报告失败', 'red');
console.log('错误信息:', response.data.message);
return null;
}
} catch (error) {
log('❌ 请求失败', 'red');
if (error.response) {
console.log('状态码:', error.response.status);
console.log('错误信息:', error.response.data);
} else {
console.log('错误:', error.message);
}
return null;
}
}
/**
* 测试5: 获取任务列表
*/
async function testGetTaskList() {
log('📋 测试5: 获取任务列表 (GET /review/tasks)', 'cyan');
try {
const response = await axios.get(`${API_BASE_URL}/review/tasks`, {
params: { page: 1, limit: 10 },
timeout: 10000,
});
if (response.data.success) {
log('✅ 获取列表成功!', 'green');
console.log(`找到 ${response.data.data.length} 个任务`);
console.log('任务列表:', JSON.stringify(response.data.data, null, 2));
console.log('分页信息:', JSON.stringify(response.data.pagination, null, 2));
return response.data.data;
} else {
log('❌ 获取列表失败', 'red');
console.log('错误信息:', response.data.message);
return null;
}
} catch (error) {
log('❌ 请求失败', 'red');
if (error.response) {
console.log('状态码:', error.response.status);
console.log('错误信息:', error.response.data);
} else {
console.log('错误:', error.message);
}
return null;
}
}
/**
* 测试6: 删除任务(可选)
*/
async function testDeleteTask(taskId) {
log('🗑️ 测试6: 删除任务 (DELETE /review/tasks/:taskId)', 'cyan');
try {
log(`📋 任务ID: ${taskId}`, 'blue');
const response = await axios.delete(`${API_BASE_URL}/review/tasks/${taskId}`, {
timeout: 10000,
});
if (response.data.success) {
log('✅ 删除成功!', 'green');
console.log('返回数据:', JSON.stringify(response.data, null, 2));
return true;
} else {
log('❌ 删除失败', 'red');
console.log('错误信息:', response.data.message);
return false;
}
} catch (error) {
log('❌ 请求失败', 'red');
if (error.response) {
console.log('状态码:', error.response.status);
console.log('错误信息:', error.response.data);
} else {
console.log('错误:', error.message);
}
return false;
}
}
// ==================== 主测试流程 ====================
async function runAllTests() {
log('🚀 开始稿件审查API测试', 'green');
separator();
// 测试1: 上传稿件
const taskId = await testUploadManuscript();
if (!taskId) {
log('❌ 上传失败,终止测试', 'red');
return;
}
separator();
// 等待2秒
await delay(2000);
// 测试2: 查询任务状态
await testGetTaskStatus(taskId);
separator();
// 测试3: 轮询直到完成
const completedTask = await pollTaskUntilComplete(taskId);
if (!completedTask) {
log('❌ 任务未能完成,跳过报告测试', 'red');
separator();
// 但仍然测试任务列表
await testGetTaskList();
separator();
return;
}
separator();
// 测试4: 获取报告
await testGetReport(taskId);
separator();
// 测试5: 获取任务列表
await testGetTaskList();
separator();
// 测试6: 删除任务(可选,取消注释以启用)
// log('⚠️ 是否删除测试任务取消注释testDeleteTask以启用', 'yellow');
// await testDeleteTask(taskId);
// separator();
log('✅ 所有测试完成!', 'green');
}
// ==================== 健康检查 ====================
async function checkHealth() {
log('🔍 检查后端服务健康状态...', 'cyan');
try {
const response = await axios.get('http://localhost:3001/health', {
timeout: 5000,
});
log('✅ 后端服务正常', 'green');
console.log('健康状态:', response.data);
return true;
} catch (error) {
log('❌ 后端服务不可用', 'red');
console.log('错误:', error.message);
log('💡 请先启动后端服务: npm run dev 或 启动后端.bat', 'yellow');
return false;
}
}
// ==================== 入口 ====================
async function main() {
console.log('\n');
log('═══════════════════════════════════════════════════════════════════════════════', 'cyan');
log(' 稿件审查API自动化测试脚本 ', 'cyan');
log('═══════════════════════════════════════════════════════════════════════════════', 'cyan');
console.log('\n');
// 健康检查
const healthy = await checkHealth();
if (!healthy) {
process.exit(1);
}
separator();
// 运行所有测试
await runAllTests();
console.log('\n');
log('═══════════════════════════════════════════════════════════════════════════════', 'cyan');
log(' 测试完成 ', 'cyan');
log('═══════════════════════════════════════════════════════════════════════════════', 'cyan');
console.log('\n');
}
main().catch(error => {
console.error('测试脚本执行失败:', error);
process.exit(1);
});