test: 添加中介导入功能测试脚本和报告模板
- 添加自动化测试脚本 test-import-upsert.js - 覆盖5个测试场景(首次导入、重复导入、更新等) - 添加测试报告模板 TEST-REPORT-TEMPLATE.md Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
301
doc/test-data/intermediary/TEST-REPORT-TEMPLATE.md
Normal file
301
doc/test-data/intermediary/TEST-REPORT-TEMPLATE.md
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
# 中介导入功能重构测试报告
|
||||||
|
|
||||||
|
## 测试目标
|
||||||
|
|
||||||
|
验证Service层重构后,使用 `importPersonBatch` 和 `importEntityBatch` 方法
|
||||||
|
(基于 `ON DUPLICATE KEY UPDATE`) 的导入功能是否正常工作。
|
||||||
|
|
||||||
|
## 重构内容
|
||||||
|
|
||||||
|
### Task 5: 重构个人中介导入Service
|
||||||
|
|
||||||
|
**文件:** `CcdiIntermediaryPersonImportServiceImpl.java`
|
||||||
|
|
||||||
|
**核心变更:**
|
||||||
|
- 移除"先查询后分类再删除再插入"的逻辑
|
||||||
|
- 更新模式(`isUpdateSupport=true`): 直接调用 `intermediaryMapper.importPersonBatch(validRecords)`
|
||||||
|
- 仅新增模式(`isUpdateSupport=false`): 先查询冲突,然后只插入无冲突数据
|
||||||
|
- 新增辅助方法:
|
||||||
|
- `saveBatchWithUpsert()`: 使用 `importPersonBatch` 进行批量UPSERT
|
||||||
|
- `getExistingPersonIdsFromDb()`: 从数据库获取已存在的证件号
|
||||||
|
- `createFailureVO()`: 创建失败记录VO(两个重载方法)
|
||||||
|
|
||||||
|
### Task 6: 重构实体中介导入Service
|
||||||
|
|
||||||
|
**文件:** `CcdiIntermediaryEntityImportServiceImpl.java`
|
||||||
|
|
||||||
|
**同样的重构逻辑**
|
||||||
|
|
||||||
|
## 测试场景
|
||||||
|
|
||||||
|
### 场景1: 个人中介 - 更新模式(第一次导入)
|
||||||
|
|
||||||
|
**目的:** 验证批量INSERT功能
|
||||||
|
|
||||||
|
**操作:**
|
||||||
|
- 上传测试数据文件(1000条个人中介数据)
|
||||||
|
- 设置 `updateSupport=true`
|
||||||
|
|
||||||
|
**预期结果:**
|
||||||
|
- 所有数据成功插入
|
||||||
|
- 状态: SUCCESS
|
||||||
|
- 成功数 = 总数
|
||||||
|
- 失败数 = 0
|
||||||
|
|
||||||
|
**实际结果:** _待测试_
|
||||||
|
|
||||||
|
**状态:** ⏳ 待执行
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 场景2: 个人中介 - 仅新增模式(重复导入)
|
||||||
|
|
||||||
|
**目的:** 验证冲突检测功能
|
||||||
|
|
||||||
|
**操作:**
|
||||||
|
- 再次上传相同的测试数据
|
||||||
|
- 设置 `updateSupport=false`
|
||||||
|
|
||||||
|
**预期结果:**
|
||||||
|
- 所有数据因为冲突而失败
|
||||||
|
- 状态: PARTIAL_SUCCESS 或 FAILURE
|
||||||
|
- 成功数 = 0
|
||||||
|
- 失败数 = 总数
|
||||||
|
- 失败原因: "该证件号码已存在"
|
||||||
|
|
||||||
|
**实际结果:** _待测试_
|
||||||
|
|
||||||
|
**状态:** ⏳ 待执行
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 场景3: 实体中介 - 更新模式(第一次导入)
|
||||||
|
|
||||||
|
**目的:** 验证实体中介批量INSERT功能
|
||||||
|
|
||||||
|
**操作:**
|
||||||
|
- 上传测试数据文件(1000条实体中介数据)
|
||||||
|
- 设置 `updateSupport=true`
|
||||||
|
|
||||||
|
**预期结果:**
|
||||||
|
- 所有数据成功插入
|
||||||
|
- 状态: SUCCESS
|
||||||
|
- 成功数 = 总数
|
||||||
|
- 失败数 = 0
|
||||||
|
|
||||||
|
**实际结果:** _待测试_
|
||||||
|
|
||||||
|
**状态:** ⏳ 待执行
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 场景4: 实体中介 - 仅新增模式(重复导入)
|
||||||
|
|
||||||
|
**目的:** 验证实体中介冲突检测功能
|
||||||
|
|
||||||
|
**操作:**
|
||||||
|
- 再次上传相同的测试数据
|
||||||
|
- 设置 `updateSupport=false`
|
||||||
|
|
||||||
|
**预期结果:**
|
||||||
|
- 所有数据因为冲突而失败
|
||||||
|
- 状态: PARTIAL_SUCCESS 或 FAILURE
|
||||||
|
- 成功数 = 0
|
||||||
|
- 失败数 = 总数
|
||||||
|
- 失败原因: "该统一社会信用代码已存在"
|
||||||
|
|
||||||
|
**实际结果:** _待测试_
|
||||||
|
|
||||||
|
**状态:** ⏳ 待执行
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 场景5: 个人中介 - 再次更新模式
|
||||||
|
|
||||||
|
**目的:** 验证 `ON DUPLICATE KEY UPDATE` 功能
|
||||||
|
|
||||||
|
**操作:**
|
||||||
|
- 第三次上传相同的测试数据
|
||||||
|
- 设置 `updateSupport=true`
|
||||||
|
|
||||||
|
**预期结果:**
|
||||||
|
- 所有数据成功更新(而不是先删除再插入)
|
||||||
|
- 状态: SUCCESS
|
||||||
|
- 成功数 = 总数
|
||||||
|
- 失败数 = 0
|
||||||
|
- 数据库中不会出现重复记录
|
||||||
|
|
||||||
|
**实际结果:** _待测试_
|
||||||
|
|
||||||
|
**状态:** ⏳ 待执行
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试方法
|
||||||
|
|
||||||
|
### 手动测试
|
||||||
|
|
||||||
|
1. **启动后端服务**
|
||||||
|
```bash
|
||||||
|
cd ruoyi-ccdi
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **访问Swagger UI**
|
||||||
|
- URL: http://localhost:8080/swagger-ui/index.html
|
||||||
|
- 找到 `/ccdi/intermediary/importPersonData` 和 `/ccdi/intermediary/importEntityData` 接口
|
||||||
|
|
||||||
|
3. **执行测试场景**
|
||||||
|
- 使用"Try it out"功能上传测试文件
|
||||||
|
- 观察响应结果
|
||||||
|
- 使用任务ID查询导入状态
|
||||||
|
- 查看失败记录
|
||||||
|
|
||||||
|
### 自动化测试
|
||||||
|
|
||||||
|
运行测试脚本:
|
||||||
|
```bash
|
||||||
|
cd doc/test-data/intermediary
|
||||||
|
node test-import-upsert.js
|
||||||
|
```
|
||||||
|
|
||||||
|
测试脚本会自动执行所有测试场景并生成报告。
|
||||||
|
|
||||||
|
## 测试数据
|
||||||
|
|
||||||
|
### 个人中介测试数据
|
||||||
|
|
||||||
|
- 文件: `doc/test-data/intermediary/个人中介黑名单测试数据_1000条_第1批.xlsx`
|
||||||
|
- 记录数: 1000
|
||||||
|
- 特点: 包含有效的身份证号码
|
||||||
|
|
||||||
|
### 实体中介测试数据
|
||||||
|
|
||||||
|
- 文件: `doc/test-data/intermediary/机构中介黑名单测试数据_1000条_第1批.xlsx`
|
||||||
|
- 记录数: 1000
|
||||||
|
- 特点: 包含有效的统一社会信用代码
|
||||||
|
|
||||||
|
## 关键验证点
|
||||||
|
|
||||||
|
### 1. 数据库层面验证
|
||||||
|
|
||||||
|
**更新模式下的UPSERT操作:**
|
||||||
|
- 检查 `ccdi_biz_intermediary` 表,确保持有相同 `person_id` 的记录只有1条
|
||||||
|
- 检查 `ccdi_enterprise_base_info` 表,确保持有相同 `social_credit_code` 的记录只有1条
|
||||||
|
|
||||||
|
**验证SQL:**
|
||||||
|
```sql
|
||||||
|
-- 检查个人中介重复记录
|
||||||
|
SELECT person_id, COUNT(*) as cnt
|
||||||
|
FROM ccdi_biz_intermediary
|
||||||
|
GROUP BY person_id
|
||||||
|
HAVING cnt > 1;
|
||||||
|
|
||||||
|
-- 检查实体中介重复记录
|
||||||
|
SELECT social_credit_code, COUNT(*) as cnt
|
||||||
|
FROM ccdi_enterprise_base_info
|
||||||
|
GROUP BY social_credit_code
|
||||||
|
HAVING cnt > 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 性能验证
|
||||||
|
|
||||||
|
**对比重构前后的性能差异:**
|
||||||
|
|
||||||
|
| 场景 | 重构前(先删后插) | 重构后(UPSERT) | 性能提升 |
|
||||||
|
|------|----------------|---------------|---------|
|
||||||
|
| 1000条首次导入 | _待测试_ | _待测试_ | _待计算_ |
|
||||||
|
| 1000条重复导入 | _待测试_ | _待测试_ | _待计算_ |
|
||||||
|
|
||||||
|
### 3. 错误处理验证
|
||||||
|
|
||||||
|
**验证失败记录的正确性:**
|
||||||
|
- 失败原因是否准确
|
||||||
|
- 失败记录的完整信息是否保留
|
||||||
|
- Redis中失败记录的存储和读取
|
||||||
|
|
||||||
|
## 测试结果汇总
|
||||||
|
|
||||||
|
| 场景 | 状态 | 通过/失败 | 备注 |
|
||||||
|
|------|------|----------|------|
|
||||||
|
| 场景1 | ⏳ 待执行 | - | 个人中介首次导入 |
|
||||||
|
| 场景2 | ⏳ 待执行 | - | 个人中介重复导入(仅新增) |
|
||||||
|
| 场景3 | ⏳ 待执行 | - | 实体中介首次导入 |
|
||||||
|
| 场景4 | ⏳ 待执行 | - | 实体中介重复导入(仅新增) |
|
||||||
|
| 场景5 | ⏳ 待执行 | - | 个人中介重复导入(更新) |
|
||||||
|
|
||||||
|
**总通过率:** 0/5 (0%)
|
||||||
|
|
||||||
|
## 问题记录
|
||||||
|
|
||||||
|
### 问题1: _问题描述_
|
||||||
|
|
||||||
|
**场景:** _相关场景_
|
||||||
|
|
||||||
|
**现象:** _具体表现_
|
||||||
|
|
||||||
|
**原因:** _根本原因_
|
||||||
|
|
||||||
|
**解决方案:** _修复方法_
|
||||||
|
|
||||||
|
**状态:** ⏳ 待解决 / ✅ 已解决
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
_测试完成后填写总体结论_
|
||||||
|
|
||||||
|
### 代码质量评估
|
||||||
|
|
||||||
|
- **可读性:** _评分_ / 10
|
||||||
|
- **可维护性:** _评分_ / 10
|
||||||
|
- **性能:** _评分_ / 10
|
||||||
|
- **错误处理:** _评分_ / 10
|
||||||
|
|
||||||
|
### 优化建议
|
||||||
|
|
||||||
|
_根据测试结果提出优化建议_
|
||||||
|
|
||||||
|
## 附录
|
||||||
|
|
||||||
|
### A. 测试环境信息
|
||||||
|
|
||||||
|
- **操作系统:** Windows 11
|
||||||
|
- **Java版本:** 17
|
||||||
|
- **Spring Boot版本:** 3.5.8
|
||||||
|
- **MySQL版本:** 8.2.0
|
||||||
|
- **Redis版本:** _待填写_
|
||||||
|
|
||||||
|
### B. 相关文件清单
|
||||||
|
|
||||||
|
- `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryPersonImportServiceImpl.java`
|
||||||
|
- `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryEntityImportServiceImpl.java`
|
||||||
|
- `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiBizIntermediaryMapper.java`
|
||||||
|
- `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiEnterpriseBaseInfoMapper.java`
|
||||||
|
- `doc/test-data/intermediary/test-import-upsert.js`
|
||||||
|
|
||||||
|
### C. Git提交信息
|
||||||
|
|
||||||
|
```
|
||||||
|
commit 7d534de
|
||||||
|
refactor: 重构Service层使用ON DUPLICATE KEY UPDATE
|
||||||
|
|
||||||
|
- 更新模式直接调用importPersonBatch/importEntityBatch
|
||||||
|
- 移除'先删除再插入'逻辑,代码简化约50%
|
||||||
|
- 添加辅助方法saveBatchWithUpsert/getExistingPersonIdsFromDb
|
||||||
|
- 添加createFailureVO重载方法简化失败记录创建
|
||||||
|
|
||||||
|
变更详情:
|
||||||
|
- CcdiIntermediaryPersonImportServiceImpl: 重构importPersonAsync方法
|
||||||
|
- CcdiIntermediaryEntityImportServiceImpl: 重构importEntityAsync方法
|
||||||
|
- 两个Service均采用统一的处理模式
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**报告生成时间:** 2026-02-08
|
||||||
|
**测试执行人:** _待填写_
|
||||||
|
**审核人:** _待填写_
|
||||||
446
doc/test-data/intermediary/test-import-upsert.js
Normal file
446
doc/test-data/intermediary/test-import-upsert.js
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
/**
|
||||||
|
* 中介导入功能测试脚本 - 验证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);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user