Files
yunrui_asset/backend/tests/operationLogs.api.test.js
T
zhang1106 73cbe4ac1b feat: 添加操作日志、危险操作确认和业务关联功能
1. 新增操作日志记录功能,记录关键操作
2. 实现危险操作确认对话框,防止误删
3. 添加业务和库房管理模块
4. 支持设备标记为空闲状态
5. 完善API文档和健康检查
6. 优化前端删除操作的确认流程
7. 添加Swagger API文档支持
8. 实现设备与业务的关联功能
9. 改进设备模型,添加空闲相关字段
10. 优化用户、角色管理操作日志
2026-03-20 17:15:14 +08:00

349 lines
10 KiB
JavaScript

process.env.JWT_SECRET = 'test-secret-key-for-jest-testing-minimum-32-chars-long';
process.env.NODE_ENV = 'test';
process.env.DB_DIALECT = 'sqlite';
process.env.DB_STORAGE = ':memory:';
const request = require('supertest');
const express = require('express');
const jwt = require('jsonwebtoken');
const { Op } = require('sequelize');
const { sequelize } = require('../db');
const OperationLog = require('../models/OperationLog');
const JWT_SECRET = 'test-secret-key-for-jest-testing-minimum-32-chars-long';
const createTestApp = () => {
const app = express();
app.use(express.json());
const authMiddleware = (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ success: false, message: '未授权' });
}
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ success: false, message: '无效的令牌' });
}
};
const operationLogsRouter = express.Router();
operationLogsRouter.get('/', authMiddleware, async (req, res) => {
try {
const {
page = 1,
pageSize = 20,
module,
operationType,
targetId,
operatorId,
keyword,
startDate,
endDate,
result
} = req.query;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const limit = Math.min(parseInt(pageSize), 100);
const where = {};
if (module && module !== 'all') {
where.module = module;
}
if (operationType && operationType !== 'all') {
where.operationType = operationType;
}
if (targetId) {
where.targetId = { [Op.like]: `%${targetId}%` };
}
if (operatorId) {
where.operatorId = operatorId;
}
if (keyword) {
where[Op.or] = [
{ operationDescription: { [Op.like]: `%${keyword}%` } },
{ targetName: { [Op.like]: `%${keyword}%` } },
{ operatorName: { [Op.like]: `%${keyword}%` } }
];
}
if (result && result !== 'all') {
where.result = result;
}
if (startDate || endDate) {
where.createdAt = {};
if (startDate) {
where.createdAt[Op.gte] = new Date(startDate);
}
if (endDate) {
const endOfDay = new Date(endDate);
endOfDay.setHours(23, 59, 59, 999);
where.createdAt[Op.lte] = endOfDay;
}
}
const { count, rows: logs } = await OperationLog.findAndCountAll({
where,
order: [['createdAt', 'DESC']],
offset,
limit
});
res.json({
success: true,
data: {
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize),
logs
}
});
} catch (error) {
console.error('获取操作日志失败:', error);
res.status(500).json({
success: false,
message: '获取操作日志失败'
});
}
});
operationLogsRouter.get('/:recordId', authMiddleware, async (req, res) => {
try {
const log = await OperationLog.findByPk(req.params.recordId);
if (!log) {
return res.status(404).json({
success: false,
message: '日志记录不存在'
});
}
res.json({
success: true,
data: log
});
} catch (error) {
console.error('获取操作日志详情失败:', error);
res.status(500).json({
success: false,
message: '获取操作日志详情失败'
});
}
});
app.use('/api/operation-logs', operationLogsRouter);
return app;
};
describe('OperationLogs API 路由测试', () => {
let app;
let authToken;
const testUser = {
userId: 'test_user_001',
username: 'testuser',
realName: '测试用户',
roleName: '管理员'
};
beforeAll(async () => {
await sequelize.sync({ force: true });
app = createTestApp();
authToken = jwt.sign(testUser, JWT_SECRET, { expiresIn: '24h' });
});
afterEach(async () => {
await OperationLog.destroy({ where: {} });
});
afterAll(async () => {
await sequelize.close();
});
const createTestLog = async (data = {}) => {
const defaultData = {
recordId: `OPLOG_API_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
module: 'device',
operationType: 'create',
operationDescription: '测试日志',
targetId: 'DEV_TEST',
targetName: '测试设备',
operatorId: testUser.userId,
operatorName: testUser.realName,
operatorRole: testUser.roleName,
result: 'success'
};
return await OperationLog.create({ ...defaultData, ...data });
};
describe('GET /api/operation-logs', () => {
test('未授权访问应该返回 401', async () => {
const response = await request(app)
.get('/api/operation-logs')
.expect(401);
expect(response.body.success).toBe(false);
});
test('应该能够获取日志列表', async () => {
await createTestLog({ recordId: 'OPLOG_LST_001' });
await createTestLog({ recordId: 'OPLOG_LST_002' });
const response = await request(app)
.get('/api/operation-logs')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data.logs).toHaveLength(2);
expect(response.body.data.total).toBe(2);
});
test('应该支持分页参数', async () => {
for (let i = 0; i < 15; i++) {
await createTestLog({ recordId: `OPLOG_PAGE_${i}` });
}
const response = await request(app)
.get('/api/operation-logs')
.query({ page: 1, pageSize: 10 })
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data.logs).toHaveLength(10);
expect(response.body.data.total).toBe(15);
expect(response.body.data.page).toBe(1);
expect(response.body.data.pageSize).toBe(10);
});
test('应该支持按 module 筛选', async () => {
await createTestLog({ module: 'device', recordId: 'OPLOG_MOD_1' });
await createTestLog({ module: 'user', recordId: 'OPLOG_MOD_2' });
await createTestLog({ module: 'role', recordId: 'OPLOG_MOD_3' });
const response = await request(app)
.get('/api/operation-logs')
.query({ module: 'device' })
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.data.logs).toHaveLength(1);
expect(response.body.data.logs[0].module).toBe('device');
});
test('应该支持按 operationType 筛选', async () => {
await createTestLog({ operationType: 'create', recordId: 'OPLOG_TYPE_1' });
await createTestLog({ operationType: 'update', recordId: 'OPLOG_TYPE_2' });
await createTestLog({ operationType: 'delete', recordId: 'OPLOG_TYPE_3' });
const response = await request(app)
.get('/api/operation-logs')
.query({ operationType: 'create' })
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.data.logs).toHaveLength(1);
expect(response.body.data.logs[0].operationType).toBe('create');
});
test('应该支持按 keyword 搜索', async () => {
await createTestLog({ operationDescription: '创建设备 SERVER_A', recordId: 'OPLOG_KW_1', targetName: '服务器A' });
await createTestLog({ operationDescription: '更新设备 SERVER_B', recordId: 'OPLOG_KW_2', targetName: '服务器B' });
await createTestLog({ operationDescription: '删除用户 USER_C', recordId: 'OPLOG_KW_3', targetName: '用户C' });
const response = await request(app)
.get('/api/operation-logs')
.query({ keyword: 'SERVER' })
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.data.logs).toHaveLength(2);
expect(response.body.data.logs.every(log => log.operationDescription.includes('SERVER'))).toBe(true);
});
test('应该支持按 result 筛选', async () => {
await createTestLog({ result: 'success', recordId: 'OPLOG_RES_1' });
await createTestLog({ result: 'failed', recordId: 'OPLOG_RES_2' });
const response = await request(app)
.get('/api/operation-logs')
.query({ result: 'failed' })
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.data.logs).toHaveLength(1);
expect(response.body.data.logs[0].result).toBe('failed');
});
test('应该支持多条件组合筛选', async () => {
await createTestLog({
module: 'device',
operationType: 'create',
result: 'success',
recordId: 'OPLOG_COMB_1'
});
await createTestLog({
module: 'device',
operationType: 'update',
result: 'success',
recordId: 'OPLOG_COMB_2'
});
await createTestLog({
module: 'user',
operationType: 'create',
result: 'success',
recordId: 'OPLOG_COMB_3'
});
const response = await request(app)
.get('/api/operation-logs')
.query({ module: 'device', operationType: 'create' })
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.data.logs).toHaveLength(1);
expect(response.body.data.logs[0].module).toBe('device');
expect(response.body.data.logs[0].operationType).toBe('create');
});
});
describe('GET /api/operation-logs/:recordId', () => {
test('应该能够获取单个日志详情', async () => {
const log = await createTestLog({
recordId: 'OPLOG_DETAIL_001',
beforeState: { name: '旧名称' },
afterState: { name: '新名称' },
metadata: { customField: '自定义值' }
});
const response = await request(app)
.get('/api/operation-logs/OPLOG_DETAIL_001')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data.recordId).toBe('OPLOG_DETAIL_001');
});
test('不存在的日志应该返回 404', async () => {
const response = await request(app)
.get('/api/operation-logs/NON_EXISTENT_LOG')
.set('Authorization', `Bearer ${authToken}`)
.expect(404);
expect(response.body.success).toBe(false);
});
});
});