Files
ccdi/doc/test-data/intermediary/test-import-upsert.js
wkc daf03e1ef0 test: 添加中介导入功能测试脚本和报告模板
- 添加自动化测试脚本 test-import-upsert.js
- 覆盖5个测试场景(首次导入、重复导入、更新等)
- 添加测试报告模板 TEST-REPORT-TEMPLATE.md

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-08 16:24:02 +08:00

447 lines
13 KiB
JavaScript
Raw 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.
/**
* 中介导入功能测试脚本 - 验证ON DUPLICATE KEY UPDATE重构
*
* 测试场景:
* 1. 更新模式 - 测试importPersonBatch/importEntityBatch的INSERT ON DUPLICATE KEY UPDATE
* 2. 仅新增模式 - 测试冲突检测和失败记录
* 3. 边界情况 - 空列表、全部冲突、部分冲突等
*/
const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const path = require('path');
// 配置
const BASE_URL = 'http://localhost:8080';
const LOGIN_URL = `${BASE_URL}/login/test`;
const PERSON_IMPORT_URL = `${BASE_URL}/ccdi/intermediary/importPersonData`;
const ENTITY_IMPORT_URL = `${BASE_URL}/ccdi/intermediary/importEntityData`;
const PERSON_STATUS_URL = `${BASE_URL}/ccdi/intermediary/person/import/status`;
const ENTITY_STATUS_URL = `${BASE_URL}/ccdi/intermediary/entity/import/status`;
const PERSON_FAILURES_URL = `${BASE_URL}/ccdi/intermediary/person/import/failures`;
const ENTITY_FAILURES_URL = `${BASE_URL}/ccdi/intermediary/entity/import/failures`;
// 测试数据文件路径
const TEST_DATA_DIR = path.join(__dirname, '../test-data/intermediary');
const PERSON_TEST_FILE = path.join(TEST_DATA_DIR, '个人中介黑名单测试数据_1000条_第1批.xlsx');
const ENTITY_TEST_FILE = path.join(TEST_DATA_DIR, '机构中介黑名单测试数据_1000条_第1批.xlsx');
let authToken = '';
// 颜色输出
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
blue: '\x1b[36m'
};
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
function logSuccess(message) {
log(`${message}`, 'green');
}
function logError(message) {
log(`${message}`, 'red');
}
function logInfo(message) {
log(` ${message}`, 'blue');
}
function logSection(title) {
console.log('\n' + '='.repeat(60));
log(title, 'yellow');
console.log('='.repeat(60));
}
/**
* 登录获取Token
*/
async function login() {
logSection('登录系统');
try {
const response = await axios.post(LOGIN_URL, {
username: 'admin',
password: 'admin123'
});
if (response.data.code === 200) {
authToken = response.data.data;
logSuccess('登录成功');
logInfo(`Token: ${authToken.substring(0, 20)}...`);
return true;
} else {
logError(`登录失败: ${response.data.msg}`);
return false;
}
} catch (error) {
logError(`登录请求失败: ${error.message}`);
return false;
}
}
/**
* 上传文件并开始导入
*/
async function importData(file, url, updateSupport, description) {
logSection(description);
if (!fs.existsSync(file)) {
logError(`测试文件不存在: ${file}`);
return null;
}
logInfo(`上传文件: ${path.basename(file)}`);
logInfo(`更新模式: ${updateSupport ? '是' : '否'}`);
try {
const form = new FormData();
form.append('file', fs.createReadStream(file));
form.append('updateSupport', updateSupport.toString());
const response = await axios.post(url, form, {
headers: {
...form.getHeaders(),
'Authorization': `Bearer ${authToken}`
}
});
if (response.data.code === 200) {
logSuccess('导入任务已提交');
logInfo(`响应信息: ${response.data.msg}`);
// 从响应中提取taskId
const match = response.data.msg.match(/任务ID: ([a-zA-Z0-9-]+)/);
if (match) {
const taskId = match[1];
logInfo(`任务ID: ${taskId}`);
return taskId;
}
} else {
logError(`导入失败: ${response.data.msg}`);
}
} catch (error) {
logError(`导入请求失败: ${error.message}`);
if (error.response) {
logError(`状态码: ${error.response.status}`);
logError(`响应数据: ${JSON.stringify(error.response.data)}`);
}
}
return null;
}
/**
* 轮询查询导入状态
*/
async function pollImportStatus(taskId, url, description, maxAttempts = 30, interval = 2000) {
logInfo(`等待导入完成...`);
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const response = await axios.get(`${url}?taskId=${taskId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.data.code === 200) {
const status = response.data.data;
logInfo(`[尝试 ${attempt}/${maxAttempts}] 状态: ${status.status}, 进度: ${status.progress}%`);
if (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS') {
logSuccess(`${description}完成!`);
logInfo(`总数: ${status.totalCount}, 成功: ${status.successCount}, 失败: ${status.failureCount}`);
return status;
} else if (status.status === 'FAILURE') {
logError(`${description}失败`);
return status;
}
}
} catch (error) {
logError(`查询状态失败: ${error.message}`);
}
await sleep(interval);
}
logError('导入超时');
return null;
}
/**
* 获取导入失败记录
*/
async function getImportFailures(taskId, url, description) {
logSection(`获取${description}失败记录`);
try {
const response = await axios.get(`${url}?taskId=${taskId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
if (response.data.code === 200) {
const failures = response.data.data;
logInfo(`失败记录数: ${failures.length}`);
if (failures.length > 0) {
logInfo('前3条失败记录:');
failures.slice(0, 3).forEach((failure, index) => {
console.log(` ${index + 1}. ${failure.errorMessage || '未知错误'}`);
});
// 保存失败记录到文件
const failureFile = path.join(__dirname, `failures_${taskId}.json`);
fs.writeFileSync(failureFile, JSON.stringify(failures, null, 2));
logInfo(`失败记录已保存到: ${failureFile}`);
}
return failures;
}
} catch (error) {
logError(`获取失败记录失败: ${error.message}`);
}
return [];
}
/**
* 辅助函数: 延迟
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 测试场景1: 个人中介 - 更新模式(第一次导入)
*/
async function testPersonImportUpdateMode() {
logSection('测试场景1: 个人中介 - 更新模式(第一次导入)');
const taskId = await importData(
PERSON_TEST_FILE,
PERSON_IMPORT_URL,
true, // 更新模式
'个人中介导入(更新模式)'
);
if (!taskId) {
logError('导入任务未创建');
return false;
}
const status = await pollImportStatus(taskId, PERSON_STATUS_URL, '个人中介导入');
if (status && (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS')) {
const failures = await getImportFailures(taskId, PERSON_FAILURES_URL, '个人中介');
logSuccess(`测试场景1完成 - 成功: ${status.successCount}, 失败: ${status.failureCount}`);
return true;
}
return false;
}
/**
* 测试场景2: 个人中介 - 仅新增模式(重复导入应失败)
*/
async function testPersonImportInsertOnly() {
logSection('测试场景2: 个人中介 - 仅新增模式(重复导入)');
const taskId = await importData(
PERSON_TEST_FILE,
PERSON_IMPORT_URL,
false, // 仅新增模式
'个人中介导入(仅新增)'
);
if (!taskId) {
logError('导入任务未创建');
return false;
}
const status = await pollImportStatus(taskId, PERSON_STATUS_URL, '个人中介导入');
if (status && (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS')) {
const failures = await getImportFailures(taskId, PERSON_FAILURES_URL, '个人中介');
// 在仅新增模式下,重复导入应该全部失败
if (failures.length > 0) {
logSuccess(`测试场景2完成 - 预期有失败记录, 实际失败: ${failures.length}`);
return true;
} else {
logError('测试场景2失败 - 预期有失败记录, 但实际没有');
return false;
}
}
return false;
}
/**
* 测试场景3: 实体中介 - 更新模式(第一次导入)
*/
async function testEntityImportUpdateMode() {
logSection('测试场景3: 实体中介 - 更新模式(第一次导入)');
const taskId = await importData(
ENTITY_TEST_FILE,
ENTITY_IMPORT_URL,
true, // 更新模式
'实体中介导入(更新模式)'
);
if (!taskId) {
logError('导入任务未创建');
return false;
}
const status = await pollImportStatus(taskId, ENTITY_STATUS_URL, '实体中介导入');
if (status && (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS')) {
const failures = await getImportFailures(taskId, ENTITY_FAILURES_URL, '实体中介');
logSuccess(`测试场景3完成 - 成功: ${status.successCount}, 失败: ${status.failureCount}`);
return true;
}
return false;
}
/**
* 测试场景4: 实体中介 - 仅新增模式(重复导入应失败)
*/
async function testEntityImportInsertOnly() {
logSection('测试场景4: 实体中介 - 仅新增模式(重复导入)');
const taskId = await importData(
ENTITY_TEST_FILE,
ENTITY_IMPORT_URL,
false, // 仅新增模式
'实体中介导入(仅新增)'
);
if (!taskId) {
logError('导入任务未创建');
return false;
}
const status = await pollImportStatus(taskId, ENTITY_STATUS_URL, '实体中介导入');
if (status && (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS')) {
const failures = await getImportFailures(taskId, ENTITY_FAILURES_URL, '实体中介');
// 在仅新增模式下,重复导入应该全部失败
if (failures.length > 0) {
logSuccess(`测试场景4完成 - 预期有失败记录, 实际失败: ${failures.length}`);
return true;
} else {
logError('测试场景4失败 - 预期有失败记录, 但实际没有');
return false;
}
}
return false;
}
/**
* 测试场景5: 个人中介 - 再次更新模式(应该更新已有数据)
*/
async function testPersonImportUpdateAgain() {
logSection('测试场景5: 个人中介 - 再次更新模式');
const taskId = await importData(
PERSON_TEST_FILE,
PERSON_IMPORT_URL,
true, // 更新模式
'个人中介导入(再次更新)'
);
if (!taskId) {
logError('导入任务未创建');
return false;
}
const status = await pollImportStatus(taskId, PERSON_STATUS_URL, '个人中介导入');
if (status && (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS')) {
const failures = await getImportFailures(taskId, PERSON_FAILURES_URL, '个人中介');
logSuccess(`测试场景5完成 - 成功: ${status.successCount}, 失败: ${status.failureCount}`);
return true;
}
return false;
}
/**
* 主测试流程
*/
async function runTests() {
console.log('\n╔════════════════════════════════════════════════════════════╗');
console.log('║ 中介导入功能测试 - ON DUPLICATE KEY UPDATE验证 ║');
console.log('╚════════════════════════════════════════════════════════════╝');
const startTime = Date.now();
const results = {
passed: 0,
failed: 0
};
// 登录
const loginSuccess = await login();
if (!loginSuccess) {
logError('无法登录,终止测试');
return;
}
// 执行测试
const tests = [
{ name: '场景1: 个人中介-更新模式(首次)', fn: testPersonImportUpdateMode },
{ name: '场景2: 个人中介-仅新增(重复)', fn: testPersonImportInsertOnly },
{ name: '场景3: 实体中介-更新模式(首次)', fn: testEntityImportUpdateMode },
{ name: '场景4: 实体中介-仅新增(重复)', fn: testEntityImportInsertOnly },
{ name: '场景5: 个人中介-再次更新', fn: testPersonImportUpdateAgain }
];
for (const test of tests) {
try {
const passed = await test.fn();
if (passed) {
results.passed++;
} else {
results.failed++;
}
await sleep(2000); // 测试之间间隔
} catch (error) {
logError(`${test.name} 执行异常: ${error.message}`);
results.failed++;
}
}
// 输出测试结果摘要
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
console.log('\n' + '='.repeat(60));
log('测试结果摘要', 'yellow');
console.log('='.repeat(60));
logSuccess(`通过: ${results.passed}/${tests.length}`);
if (results.failed > 0) {
logError(`失败: ${results.failed}/${tests.length}`);
}
logInfo(`总耗时: ${duration}`);
console.log('='.repeat(60) + '\n');
}
// 运行测试
runTests().catch(error => {
logError(`测试运行失败: ${error.message}`);
console.error(error);
process.exit(1);
});