Files
AIclinicalresearch/backend/test-tool-c-day2.mjs
HaHafeng 91cab452d1 fix(dc/tool-c): Fix special character handling and improve UX
Major fixes:
- Fix pivot transformation with special characters in column names
- Fix compute column validation for Chinese punctuation
- Fix recode dialog to fetch unique values from full dataset via new API
- Add column mapping mechanism to handle special characters

Database migration:
- Add column_mapping field to dc_tool_c_sessions table
- Migration file: 20251208_add_column_mapping

UX improvements:
- Darken table grid lines for better visibility
- Reduce column width by 40% with tooltip support
- Insert new columns next to source columns
- Preserve original row order after operations
- Add notice about 50-row preview limit

Modified files:
- Backend: SessionService, SessionController, QuickActionService, routes
- Python: pivot.py, compute.py, recode.py, binning.py, conditional.py
- Frontend: DataGrid, RecodeDialog, index.tsx, ag-grid-custom.css
- Database: schema.prisma, migration SQL

Status: Code complete, database migrated, ready for testing
2025-12-08 23:20:55 +08:00

387 lines
11 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
/**
* Tool C Day 2 API测试脚本
*
* 测试内容:
* 1. 创建测试Excel文件
* 2. 上传文件创建Session
* 3. 获取Session信息
* 4. 获取预览数据
* 5. 获取完整数据
* 6. 更新心跳
* 7. 删除Session
*
* 执行方式node test-tool-c-day2.mjs
*/
import FormData from 'form-data';
import axios from 'axios';
import * as XLSX from 'xlsx';
import { Buffer } from 'buffer';
const BASE_URL = 'http://localhost:3000';
const API_PREFIX = '/api/v1/dc/tool-c';
// ==================== 辅助函数 ====================
function printSection(title) {
console.log('\n' + '='.repeat(70));
console.log(` ${title}`);
console.log('='.repeat(70) + '\n');
}
function printSuccess(message) {
console.log('✅ ' + message);
}
function printError(message) {
console.log('❌ ' + message);
}
function printInfo(message) {
console.log(' ' + message);
}
// ==================== 创建测试Excel文件 ====================
function createTestExcelFile() {
printSection('创建测试Excel文件');
// 创建医疗数据
const testData = [
{ patient_id: 'P001', name: '张三', age: 25, gender: '男', diagnosis: '感冒', sbp: 120, dbp: 80 },
{ patient_id: 'P002', name: '李四', age: 65, gender: '女', diagnosis: '高血压', sbp: 150, dbp: 95 },
{ patient_id: 'P003', name: '王五', age: 45, gender: '男', diagnosis: '糖尿病', sbp: 135, dbp: 85 },
{ patient_id: 'P004', name: '赵六', age: 70, gender: '女', diagnosis: '冠心病', sbp: 160, dbp: 100 },
{ patient_id: 'P005', name: '钱七', age: 35, gender: '男', diagnosis: '胃炎', sbp: 110, dbp: 70 },
{ patient_id: 'P006', name: '孙八', age: 55, gender: '女', diagnosis: '肺炎', sbp: 125, dbp: 82 },
{ patient_id: 'P007', name: '周九', age: 48, gender: '男', diagnosis: '肝炎', sbp: 130, dbp: 88 },
{ patient_id: 'P008', name: '吴十', age: 62, gender: '女', diagnosis: '关节炎', sbp: 145, dbp: 92 },
];
// 创建工作簿
const ws = XLSX.utils.json_to_sheet(testData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
// 生成Buffer
const excelBuffer = XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' });
printSuccess(`测试文件创建成功: ${testData.length}行 x ${Object.keys(testData[0]).length}`);
printInfo(`文件大小: ${(excelBuffer.length / 1024).toFixed(2)} KB`);
return {
buffer: excelBuffer,
fileName: 'test-medical-data.xlsx',
expectedRows: testData.length,
expectedCols: Object.keys(testData[0]).length,
};
}
// ==================== API测试函数 ====================
async function testUploadFile(excelData) {
printSection('测试1: 上传Excel文件创建Session');
try {
const form = new FormData();
form.append('file', excelData.buffer, {
filename: excelData.fileName,
contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
printInfo(`上传文件: ${excelData.fileName}`);
const response = await axios.post(
`${BASE_URL}${API_PREFIX}/sessions/upload`,
form,
{
headers: form.getHeaders(),
timeout: 10000,
}
);
if (response.status === 201 && response.data.success) {
printSuccess('文件上传成功');
console.log('响应数据:', JSON.stringify(response.data.data, null, 2));
const { sessionId, totalRows, totalCols, columns } = response.data.data;
// 验证数据
if (totalRows === excelData.expectedRows && totalCols === excelData.expectedCols) {
printSuccess(`数据验证通过: ${totalRows}行 x ${totalCols}`);
} else {
printError(`数据不匹配: 预期${excelData.expectedRows}行x${excelData.expectedCols}列, 实际${totalRows}行x${totalCols}`);
}
printInfo(`Session ID: ${sessionId}`);
printInfo(`列名: ${columns.join(', ')}`);
return sessionId;
} else {
printError('上传失败: ' + JSON.stringify(response.data));
return null;
}
} catch (error) {
printError('上传异常: ' + (error.response?.data?.error || error.message));
if (error.response) {
console.log('错误详情:', JSON.stringify(error.response.data, null, 2));
}
return null;
}
}
async function testGetSession(sessionId) {
printSection('测试2: 获取Session信息');
try {
printInfo(`Session ID: ${sessionId}`);
const response = await axios.get(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}`,
{ timeout: 5000 }
);
if (response.status === 200 && response.data.success) {
printSuccess('Session信息获取成功');
console.log('Session信息:', JSON.stringify(response.data.data, null, 2));
return true;
} else {
printError('获取失败');
return false;
}
} catch (error) {
printError('获取异常: ' + (error.response?.data?.error || error.message));
return false;
}
}
async function testGetPreviewData(sessionId) {
printSection('测试3: 获取预览数据前100行');
try {
printInfo(`Session ID: ${sessionId}`);
const response = await axios.get(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}/preview`,
{ timeout: 10000 }
);
if (response.status === 200 && response.data.success) {
printSuccess('预览数据获取成功');
const { totalRows, previewRows, previewData } = response.data.data;
printInfo(`总行数: ${totalRows}, 预览行数: ${previewRows}`);
printInfo(`预览数据前3行:`);
console.log(JSON.stringify(previewData.slice(0, 3), null, 2));
return true;
} else {
printError('获取预览数据失败');
return false;
}
} catch (error) {
printError('获取预览数据异常: ' + (error.response?.data?.error || error.message));
return false;
}
}
async function testGetFullData(sessionId) {
printSection('测试4: 获取完整数据');
try {
printInfo(`Session ID: ${sessionId}`);
const response = await axios.get(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}/full`,
{ timeout: 10000 }
);
if (response.status === 200 && response.data.success) {
printSuccess('完整数据获取成功');
const { totalRows, data } = response.data.data;
printInfo(`总行数: ${totalRows}`);
printInfo(`完整数据前2行:`);
console.log(JSON.stringify(data.slice(0, 2), null, 2));
return true;
} else {
printError('获取完整数据失败');
return false;
}
} catch (error) {
printError('获取完整数据异常: ' + (error.response?.data?.error || error.message));
return false;
}
}
async function testUpdateHeartbeat(sessionId) {
printSection('测试5: 更新心跳');
try {
printInfo(`Session ID: ${sessionId}`);
const response = await axios.post(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}/heartbeat`,
{},
{ timeout: 5000 }
);
if (response.status === 200 && response.data.success) {
printSuccess('心跳更新成功');
console.log('新过期时间:', response.data.data.expiresAt);
return true;
} else {
printError('心跳更新失败');
return false;
}
} catch (error) {
printError('心跳更新异常: ' + (error.response?.data?.error || error.message));
return false;
}
}
async function testDeleteSession(sessionId) {
printSection('测试6: 删除Session');
try {
printInfo(`Session ID: ${sessionId}`);
const response = await axios.delete(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}`,
{ timeout: 5000 }
);
if (response.status === 200 && response.data.success) {
printSuccess('Session删除成功');
return true;
} else {
printError('Session删除失败');
return false;
}
} catch (error) {
printError('Session删除异常: ' + (error.response?.data?.error || error.message));
return false;
}
}
async function testGetDeletedSession(sessionId) {
printSection('测试7: 验证Session已删除');
try {
printInfo(`尝试获取已删除的Session: ${sessionId}`);
const response = await axios.get(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}`,
{ timeout: 5000 }
);
printError('Session仍然存在不应该');
return false;
} catch (error) {
if (error.response?.status === 404) {
printSuccess('Session已正确删除返回404');
return true;
} else {
printError('未预期的错误: ' + error.message);
return false;
}
}
}
// ==================== 主测试函数 ====================
async function runAllTests() {
console.log('\n' + '🚀'.repeat(35));
console.log(' Tool C Day 2 API测试');
console.log('🚀'.repeat(35));
const results = {};
try {
// 创建测试文件
const excelData = createTestExcelFile();
// 测试1: 上传文件
const sessionId = await testUploadFile(excelData);
results['上传文件'] = !!sessionId;
if (!sessionId) {
printError('上传失败,后续测试无法继续');
return results;
}
// 等待1秒
await new Promise(resolve => setTimeout(resolve, 1000));
// 测试2: 获取Session信息
results['获取Session'] = await testGetSession(sessionId);
await new Promise(resolve => setTimeout(resolve, 500));
// 测试3: 获取预览数据
results['获取预览数据'] = await testGetPreviewData(sessionId);
await new Promise(resolve => setTimeout(resolve, 500));
// 测试4: 获取完整数据
results['获取完整数据'] = await testGetFullData(sessionId);
await new Promise(resolve => setTimeout(resolve, 500));
// 测试5: 更新心跳
results['更新心跳'] = await testUpdateHeartbeat(sessionId);
await new Promise(resolve => setTimeout(resolve, 500));
// 测试6: 删除Session
results['删除Session'] = await testDeleteSession(sessionId);
await new Promise(resolve => setTimeout(resolve, 500));
// 测试7: 验证删除
results['验证删除'] = await testGetDeletedSession(sessionId);
} catch (error) {
printError('测试过程中发生异常: ' + error.message);
console.error(error);
}
// 汇总结果
printSection('测试结果汇总');
let passed = 0;
let total = 0;
for (const [testName, result] of Object.entries(results)) {
total++;
if (result) {
passed++;
console.log(`${testName.padEnd(20)}: ✅ 通过`);
} else {
console.log(`${testName.padEnd(20)}: ❌ 失败`);
}
}
console.log('\n' + '-'.repeat(70));
console.log(`总计: ${passed}/${total} 通过 (${((passed/total)*100).toFixed(1)}%)`);
console.log('-'.repeat(70));
if (passed === total) {
console.log('\n🎉 所有测试通过Day 2 Session管理功能完成\n');
} else {
console.log(`\n⚠️ 有 ${total - passed} 个测试失败,请检查\n`);
}
}
// 执行测试
runAllTests()
.then(() => {
console.log('测试完成');
process.exit(0);
})
.catch((error) => {
console.error('测试失败:', error);
process.exit(1);
});