- 创建统一的下载错误处理模块 (lib/download-errors.ts) - 实现13种错误类型分类和中文错误消息 - 后端统一使用 createDownloadError 创建标准化错误响应 - 前端统一使用 parseDownloadError 解析错误并显示 - 修复正式系统数据库Schema,添加产品全案相关字段和表 - 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段 - 创建 material_assets, api_keys, api_call_logs 表
389 lines
9.9 KiB
TypeScript
389 lines
9.9 KiB
TypeScript
/**
|
||
* 影子系统功能测试脚本
|
||
*
|
||
* 测试功能:
|
||
* 1. 用户登录
|
||
* 2. 用户角色切换(管理员/普通用户)
|
||
* 3. 用户状态管理
|
||
*
|
||
* 使用方法:
|
||
* npx tsx scripts/test-shadow-system.ts
|
||
*/
|
||
|
||
import { PrismaClient } from '@prisma/client';
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
// 测试配置
|
||
const SHADOW_SYSTEM_URL = process.env.SHADOW_SYSTEM_URL || 'http://192.168.58.20:5320';
|
||
const TEST_USER_EMAIL = process.env.TEST_USER_EMAIL || '';
|
||
const TEST_USER_PASSWORD = process.env.TEST_USER_PASSWORD || '';
|
||
|
||
interface TestResult {
|
||
name: string;
|
||
passed: boolean;
|
||
error?: string;
|
||
details?: any;
|
||
}
|
||
|
||
const results: TestResult[] = [];
|
||
|
||
async function test(name: string, testFn: () => Promise<any>): Promise<void> {
|
||
try {
|
||
console.log(`\n🧪 测试: ${name}`);
|
||
const details = await testFn();
|
||
results.push({ name, passed: true, details });
|
||
console.log(` ✅ 通过`);
|
||
} catch (error: any) {
|
||
results.push({ name, passed: false, error: error.message });
|
||
console.log(` ❌ 失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
async function main() {
|
||
console.log('========================================');
|
||
console.log('影子系统功能测试');
|
||
console.log('========================================');
|
||
console.log(`测试目标: ${SHADOW_SYSTEM_URL}`);
|
||
console.log(`数据库: ${process.env.DATABASE_URL?.split('@')[1] || '未设置'}`);
|
||
console.log('');
|
||
|
||
// 测试1: 检查数据库连接
|
||
await test('数据库连接', async () => {
|
||
await prisma.$connect();
|
||
const count = await prisma.user.count();
|
||
return { totalUsers: count };
|
||
});
|
||
|
||
// 测试2: 检查用户状态字段
|
||
await test('用户状态字段检查', async () => {
|
||
const users = await prisma.user.findMany({
|
||
take: 5,
|
||
select: {
|
||
id: true,
|
||
email: true,
|
||
username: true,
|
||
status: true,
|
||
isActive: true,
|
||
role: true,
|
||
lastLoginAt: true,
|
||
loginCount: true,
|
||
},
|
||
});
|
||
|
||
const hasStatus = users.every(u => u.status !== null && u.status !== undefined);
|
||
const hasIsActive = users.every(u => u.isActive !== null && u.isActive !== undefined);
|
||
|
||
if (!hasStatus || !hasIsActive) {
|
||
throw new Error('用户状态字段缺失');
|
||
}
|
||
|
||
return {
|
||
sampleUsers: users.map(u => ({
|
||
email: u.email,
|
||
status: u.status,
|
||
isActive: u.isActive,
|
||
role: u.role,
|
||
hasLastLoginAt: u.lastLoginAt !== null,
|
||
loginCount: u.loginCount,
|
||
})),
|
||
};
|
||
});
|
||
|
||
// 测试3: 检查部门外键关联
|
||
await test('部门外键关联检查', async () => {
|
||
const usersWithDept = await prisma.user.findMany({
|
||
where: {
|
||
department: { not: '' },
|
||
},
|
||
take: 5,
|
||
select: {
|
||
id: true,
|
||
email: true,
|
||
department: true,
|
||
departmentId: true,
|
||
departmentRef: {
|
||
select: {
|
||
id: true,
|
||
name: true,
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
const allHaveDepartmentId = usersWithDept.every(u => u.departmentId !== null);
|
||
const allHaveDepartmentRef = usersWithDept.every(u => u.departmentRef !== null);
|
||
const namesMatch = usersWithDept.every(
|
||
u => !u.departmentRef || u.department === u.departmentRef.name
|
||
);
|
||
|
||
if (!allHaveDepartmentId) {
|
||
throw new Error('部分用户缺少 departmentId');
|
||
}
|
||
if (!allHaveDepartmentRef) {
|
||
throw new Error('部分用户缺少 departmentRef 关联');
|
||
}
|
||
if (!namesMatch) {
|
||
throw new Error('部门名称与关联不匹配');
|
||
}
|
||
|
||
return {
|
||
sampleUsers: usersWithDept.map(u => ({
|
||
email: u.email,
|
||
department: u.department,
|
||
departmentId: u.departmentId,
|
||
departmentRefName: u.departmentRef?.name,
|
||
})),
|
||
};
|
||
});
|
||
|
||
// 测试4: 查找测试用户(管理员和普通用户)
|
||
await test('查找测试用户', async () => {
|
||
const adminUser = await prisma.user.findFirst({
|
||
where: {
|
||
role: 'ADMIN',
|
||
status: 'active',
|
||
},
|
||
select: {
|
||
id: true,
|
||
email: true,
|
||
username: true,
|
||
role: true,
|
||
status: true,
|
||
},
|
||
});
|
||
|
||
const normalUser = await prisma.user.findFirst({
|
||
where: {
|
||
role: 'USER',
|
||
status: 'active',
|
||
},
|
||
select: {
|
||
id: true,
|
||
email: true,
|
||
username: true,
|
||
role: true,
|
||
status: true,
|
||
},
|
||
});
|
||
|
||
if (!adminUser || !normalUser) {
|
||
throw new Error('未找到足够的测试用户(需要至少1个管理员和1个普通用户)');
|
||
}
|
||
|
||
return {
|
||
adminUser: {
|
||
id: adminUser.id,
|
||
email: adminUser.email,
|
||
username: adminUser.username,
|
||
role: adminUser.role,
|
||
status: adminUser.status,
|
||
},
|
||
normalUser: {
|
||
id: normalUser.id,
|
||
email: normalUser.email,
|
||
username: normalUser.username,
|
||
role: normalUser.role,
|
||
status: normalUser.status,
|
||
},
|
||
};
|
||
});
|
||
|
||
// 测试5: 测试角色切换(模拟)
|
||
await test('角色切换功能(数据验证)', async () => {
|
||
// 找到一个普通用户
|
||
const user = await prisma.user.findFirst({
|
||
where: {
|
||
role: 'USER',
|
||
status: 'active',
|
||
},
|
||
});
|
||
|
||
if (!user) {
|
||
throw new Error('未找到可测试的普通用户');
|
||
}
|
||
|
||
const originalRole = user.role;
|
||
|
||
// 验证可以切换到ADMIN
|
||
await prisma.user.update({
|
||
where: { id: user.id },
|
||
data: { role: 'ADMIN' },
|
||
});
|
||
|
||
const updatedUser = await prisma.user.findUnique({
|
||
where: { id: user.id },
|
||
select: { role: true },
|
||
});
|
||
|
||
if (updatedUser?.role !== 'ADMIN') {
|
||
throw new Error('角色切换失败');
|
||
}
|
||
|
||
// 恢复原角色
|
||
await prisma.user.update({
|
||
where: { id: user.id },
|
||
data: { role: originalRole },
|
||
});
|
||
|
||
return {
|
||
testUserId: user.id,
|
||
originalRole,
|
||
switchedToAdmin: true,
|
||
restored: true,
|
||
};
|
||
});
|
||
|
||
// 测试6: 测试状态切换(模拟)
|
||
await test('状态切换功能(数据验证)', async () => {
|
||
const user = await prisma.user.findFirst({
|
||
where: {
|
||
status: 'active',
|
||
},
|
||
});
|
||
|
||
if (!user) {
|
||
throw new Error('未找到可测试的用户');
|
||
}
|
||
|
||
const originalStatus = user.status;
|
||
const originalIsActive = user.isActive;
|
||
|
||
// 测试切换到suspended
|
||
await prisma.user.update({
|
||
where: { id: user.id },
|
||
data: {
|
||
status: 'suspended',
|
||
isActive: false,
|
||
},
|
||
});
|
||
|
||
const suspendedUser = await prisma.user.findUnique({
|
||
where: { id: user.id },
|
||
select: { status: true, isActive: true },
|
||
});
|
||
|
||
if (suspendedUser?.status !== 'suspended' || suspendedUser?.isActive !== false) {
|
||
throw new Error('状态切换失败');
|
||
}
|
||
|
||
// 测试切换回active
|
||
await prisma.user.update({
|
||
where: { id: user.id },
|
||
data: {
|
||
status: 'active',
|
||
isActive: true,
|
||
},
|
||
});
|
||
|
||
const activeUser = await prisma.user.findUnique({
|
||
where: { id: user.id },
|
||
select: { status: true, isActive: true },
|
||
});
|
||
|
||
if (activeUser?.status !== 'active' || activeUser?.isActive !== true) {
|
||
throw new Error('状态恢复失败');
|
||
}
|
||
|
||
return {
|
||
testUserId: user.id,
|
||
originalStatus,
|
||
switchedToSuspended: true,
|
||
restoredToActive: true,
|
||
};
|
||
});
|
||
|
||
// 测试7: 验证双写逻辑(status 和 isActive 的一致性)
|
||
await test('双写逻辑验证(status 和 isActive 一致性)', async () => {
|
||
const inconsistencies = await prisma.user.findMany({
|
||
where: {
|
||
OR: [
|
||
{ status: 'active', isActive: false },
|
||
{ status: { not: 'active' }, isActive: true },
|
||
],
|
||
},
|
||
take: 10,
|
||
select: {
|
||
id: true,
|
||
email: true,
|
||
status: true,
|
||
isActive: true,
|
||
},
|
||
});
|
||
|
||
if (inconsistencies.length > 0) {
|
||
throw new Error(
|
||
`发现 ${inconsistencies.length} 个用户 status 和 isActive 不一致`
|
||
);
|
||
}
|
||
|
||
return {
|
||
checked: true,
|
||
inconsistencies: 0,
|
||
};
|
||
});
|
||
|
||
// 测试8: 统计信息
|
||
await test('数据统计', async () => {
|
||
const totalUsers = await prisma.user.count();
|
||
const activeUsers = await prisma.user.count({ where: { status: 'active' } });
|
||
const pendingUsers = await prisma.user.count({ where: { status: 'pending' } });
|
||
const suspendedUsers = await prisma.user.count({ where: { status: 'suspended' } });
|
||
const archivedUsers = await prisma.user.count({ where: { status: 'archived' } });
|
||
const adminUsers = await prisma.user.count({ where: { role: 'ADMIN' } });
|
||
const normalUsers = await prisma.user.count({ where: { role: 'USER' } });
|
||
const usersWithDept = await prisma.user.count({ where: { departmentId: { not: null } } });
|
||
|
||
return {
|
||
totalUsers,
|
||
byStatus: {
|
||
active: activeUsers,
|
||
pending: pendingUsers,
|
||
suspended: suspendedUsers,
|
||
archived: archivedUsers,
|
||
},
|
||
byRole: {
|
||
admin: adminUsers,
|
||
user: normalUsers,
|
||
},
|
||
usersWithDepartmentId: usersWithDept,
|
||
};
|
||
});
|
||
|
||
// 输出测试结果
|
||
console.log('\n========================================');
|
||
console.log('测试结果汇总');
|
||
console.log('========================================');
|
||
|
||
const passed = results.filter(r => r.passed).length;
|
||
const failed = results.filter(r => !r.passed).length;
|
||
|
||
results.forEach(result => {
|
||
const icon = result.passed ? '✅' : '❌';
|
||
console.log(`${icon} ${result.name}`);
|
||
if (!result.passed && result.error) {
|
||
console.log(` 错误: ${result.error}`);
|
||
}
|
||
});
|
||
|
||
console.log('\n========================================');
|
||
console.log(`总计: ${results.length} 个测试`);
|
||
console.log(`✅ 通过: ${passed}`);
|
||
console.log(`❌ 失败: ${failed}`);
|
||
console.log('========================================\n');
|
||
|
||
if (failed > 0) {
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
main()
|
||
.catch((error) => {
|
||
console.error('测试执行失败:', error);
|
||
process.exit(1);
|
||
})
|
||
.finally(async () => {
|
||
await prisma.$disconnect();
|
||
});
|
||
|