WuYongJie 75db836d29 feat(3D核心功能): 完成所有必需实现任务
- 文件上传验证: ModelUploader组件增强、上传API错误处理
- GLTF解析器: 增强节点提取和元数据
- 板材编辑: PanelEditor客户端验证 + panels API服务端验证
- 装配步骤编辑: 修复未使用代码,添加预览按钮
- 装配动画播放: AssemblyPlayer步骤导航边界检查优化
- 分享链接: 完整CRUD API(创建、获取、删除),含验证和过期检查

跳过可选测试任务以加快MVP开发
2025-12-22 16:18:13 +08:00

440 lines
12 KiB
TypeScript

/**
* 操作日志 API 集成测试
*
* 测试范围:
* - 日志记录
* - 日志查询
* - 日志筛选
* - 日志统计
*/
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
import { PrismaClient, ActivityType } from '@prisma/client';
import { setupTestEnvironment, cleanDatabase } from '../helpers/test-utils';
const prisma = new PrismaClient();
describe('操作日志 API 测试', () => {
let testEnv: any;
beforeAll(async () => {
await cleanDatabase();
testEnv = await setupTestEnvironment();
});
afterAll(async () => {
await cleanDatabase();
await prisma.$disconnect();
});
describe('日志记录', () => {
it('应该能记录用户登录日志', async () => {
const log = await prisma.activityLog.create({
data: {
userId: testEnv.users.designer.id,
type: ActivityType.USER_LOGIN,
action: '用户登录',
metadata: {
ip: '192.168.1.100',
userAgent: 'Mozilla/5.0',
device: 'Desktop'
}
}
});
expect(log).toBeDefined();
expect(log.type).toBe(ActivityType.USER_LOGIN);
expect(log.metadata).toBeDefined();
});
it('应该能记录产品创建日志', async () => {
const log = await prisma.activityLog.create({
data: {
userId: testEnv.users.designer.id,
type: ActivityType.PRODUCT_CREATE,
targetType: 'Product',
targetId: testEnv.product.id,
action: '创建产品',
changes: {
modelNumber: testEnv.product.modelNumber,
name: testEnv.product.name
}
}
});
expect(log.targetType).toBe('Product');
expect(log.targetId).toBe(testEnv.product.id);
expect(log.changes).toBeDefined();
});
it('应该能记录产品更新日志', async () => {
const log = await prisma.activityLog.create({
data: {
userId: testEnv.users.designer.id,
type: ActivityType.PRODUCT_UPDATE,
targetType: 'Product',
targetId: testEnv.product.id,
action: '更新产品信息',
changes: {
before: { name: '旧名称' },
after: { name: '新名称' }
}
}
});
expect(log.type).toBe(ActivityType.PRODUCT_UPDATE);
const changes = log.changes as any;
expect(changes.before).toBeDefined();
expect(changes.after).toBeDefined();
});
it('应该能记录 SKU 交接日志', async () => {
const log = await prisma.activityLog.create({
data: {
userId: testEnv.users.admin.id,
type: ActivityType.SKU_TRANSFER,
targetType: 'ProductSKU',
targetId: testEnv.sku.id,
action: 'SKU 交接',
changes: {
fromUserId: testEnv.users.designer.id,
toUserId: testEnv.users.teamLeader.id,
reason: '人员调整'
}
}
});
expect(log.type).toBe(ActivityType.SKU_TRANSFER);
const changes = log.changes as any;
expect(changes.fromUserId).toBeDefined();
expect(changes.toUserId).toBeDefined();
});
it('应该能记录文件上传日志', async () => {
const log = await prisma.activityLog.create({
data: {
userId: testEnv.users.designer.id,
type: ActivityType.FILE_UPLOAD,
action: '上传 3D 模型',
metadata: {
fileName: 'sofa-model.glb',
fileSize: 1024000,
fileType: 'model/gltf-binary'
}
}
});
expect(log.type).toBe(ActivityType.FILE_UPLOAD);
const metadata = log.metadata as any;
expect(metadata.fileName).toBe('sofa-model.glb');
});
it('应该能记录分享链接创建日志', async () => {
const log = await prisma.activityLog.create({
data: {
userId: testEnv.users.designer.id,
type: ActivityType.SHARE_LINK_CREATE,
targetType: 'ShareLink',
targetId: 'share-link-id',
action: '创建分享链接',
metadata: {
productId: testEnv.product.id,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
}
}
});
expect(log.type).toBe(ActivityType.SHARE_LINK_CREATE);
});
});
describe('日志查询', () => {
beforeAll(async () => {
// 创建测试日志数据
const logTypes = [
ActivityType.USER_LOGIN,
ActivityType.PRODUCT_CREATE,
ActivityType.PRODUCT_UPDATE,
ActivityType.SKU_CREATE,
ActivityType.FILE_UPLOAD
];
for (const type of logTypes) {
await prisma.activityLog.create({
data: {
userId: testEnv.users.designer.id,
type,
action: `测试操作 - ${type}`
}
});
}
});
it('应该能查询所有日志', async () => {
const logs = await prisma.activityLog.findMany({
include: {
user: {
select: {
id: true,
name: true,
email: true
}
}
},
orderBy: {
createdAt: 'desc'
}
});
expect(logs.length).toBeGreaterThan(0);
});
it('应该能查询指定用户的日志', async () => {
const userLogs = await prisma.activityLog.findMany({
where: {
userId: testEnv.users.designer.id
}
});
expect(userLogs.length).toBeGreaterThan(0);
userLogs.forEach(log => {
expect(log.userId).toBe(testEnv.users.designer.id);
});
});
it('应该能按操作类型筛选', async () => {
const loginLogs = await prisma.activityLog.findMany({
where: {
type: ActivityType.USER_LOGIN
}
});
loginLogs.forEach(log => {
expect(log.type).toBe(ActivityType.USER_LOGIN);
});
});
it('应该能按目标类型筛选', async () => {
const productLogs = await prisma.activityLog.findMany({
where: {
targetType: 'Product'
}
});
productLogs.forEach(log => {
expect(log.targetType).toBe('Product');
});
});
it('应该能查询指定对象的日志', async () => {
const objectLogs = await prisma.activityLog.findMany({
where: {
targetType: 'Product',
targetId: testEnv.product.id
},
orderBy: {
createdAt: 'desc'
}
});
objectLogs.forEach(log => {
expect(log.targetId).toBe(testEnv.product.id);
});
});
it('应该能按时间范围查询', async () => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const recentLogs = await prisma.activityLog.findMany({
where: {
createdAt: {
gte: yesterday
}
}
});
expect(recentLogs.length).toBeGreaterThan(0);
recentLogs.forEach(log => {
expect(log.createdAt.getTime()).toBeGreaterThanOrEqual(yesterday.getTime());
});
});
});
describe('日志分页', () => {
it('应该支持分页查询', async () => {
const pageSize = 10;
const page1 = await prisma.activityLog.findMany({
take: pageSize,
skip: 0,
orderBy: {
createdAt: 'desc'
}
});
expect(page1.length).toBeLessThanOrEqual(pageSize);
});
it('应该能获取总数', async () => {
const total = await prisma.activityLog.count();
expect(total).toBeGreaterThan(0);
});
it('应该能获取筛选后的总数', async () => {
const count = await prisma.activityLog.count({
where: {
userId: testEnv.users.designer.id
}
});
expect(count).toBeGreaterThan(0);
});
});
describe('日志统计', () => {
it('应该能统计各操作类型的数量', async () => {
const typeCounts = await prisma.activityLog.groupBy({
by: ['type'],
_count: true
});
expect(typeCounts.length).toBeGreaterThan(0);
typeCounts.forEach(item => {
expect(item.type).toBeDefined();
expect(item._count).toBeGreaterThan(0);
});
});
it('应该能统计用户活跃度', async () => {
const userActivity = await prisma.activityLog.groupBy({
by: ['userId'],
_count: true,
orderBy: {
_count: {
userId: 'desc'
}
}
});
expect(userActivity.length).toBeGreaterThan(0);
userActivity.forEach(item => {
expect(item.userId).toBeDefined();
expect(item._count).toBeGreaterThan(0);
});
});
it('应该能统计每日操作量', async () => {
const today = new Date();
today.setHours(0, 0, 0, 0);
const todayCount = await prisma.activityLog.count({
where: {
createdAt: {
gte: today
}
}
});
expect(todayCount).toBeGreaterThanOrEqual(0);
});
});
describe('安全日志', () => {
it('应该能记录登录失败日志', async () => {
const log = await prisma.activityLog.create({
data: {
userId: testEnv.users.designer.id,
type: ActivityType.USER_LOGIN_FAILED,
action: '登录失败',
metadata: {
ip: '192.168.1.100',
reason: '密码错误',
attemptCount: 3
}
}
});
expect(log.type).toBe(ActivityType.USER_LOGIN_FAILED);
const metadata = log.metadata as any;
expect(metadata.reason).toBe('密码错误');
});
it('应该能查询异常登录记录', async () => {
const failedLogins = await prisma.activityLog.findMany({
where: {
type: ActivityType.USER_LOGIN_FAILED
},
orderBy: {
createdAt: 'desc'
}
});
failedLogins.forEach(log => {
expect(log.type).toBe(ActivityType.USER_LOGIN_FAILED);
});
});
it('应该能记录数据导出日志', async () => {
const log = await prisma.activityLog.create({
data: {
userId: testEnv.users.admin.id,
type: ActivityType.DATA_EXPORT,
action: '导出产品数据',
metadata: {
exportType: 'products',
recordCount: 100,
format: 'xlsx'
}
}
});
expect(log.type).toBe(ActivityType.DATA_EXPORT);
});
});
describe('日志保留策略', () => {
it('应该能删除过期日志', async () => {
// 创建一个旧日志
const oldDate = new Date();
oldDate.setMonth(oldDate.getMonth() - 7); // 7个月前
const oldLog = await prisma.activityLog.create({
data: {
userId: testEnv.users.designer.id,
type: ActivityType.USER_LOGIN,
action: '旧日志',
createdAt: oldDate
}
});
// 删除 6 个月前的日志
const sixMonthsAgo = new Date();
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
const deleteResult = await prisma.activityLog.deleteMany({
where: {
createdAt: {
lt: sixMonthsAgo
}
}
});
expect(deleteResult.count).toBeGreaterThan(0);
});
it('应该能统计日志存储量', async () => {
const totalLogs = await prisma.activityLog.count();
const last30Days = await prisma.activityLog.count({
where: {
createdAt: {
gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
}
}
});
expect(totalLogs).toBeGreaterThanOrEqual(last30Days);
});
});
});