1. 新增操作日志记录功能,记录关键操作 2. 实现危险操作确认对话框,防止误删 3. 添加业务和库房管理模块 4. 支持设备标记为空闲状态 5. 完善API文档和健康检查 6. 优化前端删除操作的确认流程 7. 添加Swagger API文档支持 8. 实现设备与业务的关联功能 9. 改进设备模型,添加空闲相关字段 10. 优化用户、角色管理操作日志
478 lines
13 KiB
JavaScript
478 lines
13 KiB
JavaScript
const { sequelize } = require('../db');
|
|
const OperationLog = require('../models/OperationLog');
|
|
const {
|
|
logOperation,
|
|
logDeviceOperation,
|
|
logUserOperation,
|
|
logRoleOperation
|
|
} = require('../utils/operationLogger');
|
|
|
|
describe('operationLogger 工具函数测试', () => {
|
|
beforeAll(async () => {
|
|
await sequelize.sync({ force: true });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await OperationLog.destroy({ where: {} });
|
|
});
|
|
|
|
describe('logOperation 基础函数', () => {
|
|
test('应该能够记录基本操作日志', async () => {
|
|
const mockReq = {
|
|
user: {
|
|
userId: 'user_test_001',
|
|
realName: '测试用户',
|
|
roleName: '管理员'
|
|
},
|
|
headers: {
|
|
'x-forwarded-for': '192.168.1.100',
|
|
'user-agent': 'Mozilla/5.0 Test Browser'
|
|
}
|
|
};
|
|
|
|
await logOperation({
|
|
module: 'device',
|
|
operationType: 'create',
|
|
operationDescription: '测试创建设备',
|
|
targetId: 'DEV_TEST_001',
|
|
targetName: '测试设备',
|
|
beforeState: null,
|
|
afterState: { name: '测试设备', status: 'running' },
|
|
result: 'success',
|
|
req: mockReq
|
|
});
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { targetId: 'DEV_TEST_001' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].module).toBe('device');
|
|
expect(logs[0].operationType).toBe('create');
|
|
expect(logs[0].operationDescription).toBe('测试创建设备');
|
|
expect(logs[0].targetId).toBe('DEV_TEST_001');
|
|
expect(logs[0].targetName).toBe('测试设备');
|
|
expect(logs[0].operatorId).toBe('user_test_001');
|
|
expect(logs[0].operatorName).toBe('测试用户');
|
|
expect(logs[0].operatorRole).toBe('管理员');
|
|
expect(logs[0].ipAddress).toBe('192.168.1.100');
|
|
expect(logs[0].userAgent).toBe('Mozilla/5.0 Test Browser');
|
|
expect(logs[0].result).toBe('success');
|
|
});
|
|
|
|
test('应该能够处理没有用户信息的请求', async () => {
|
|
const mockReq = {
|
|
user: null,
|
|
headers: {}
|
|
};
|
|
|
|
await logOperation({
|
|
module: 'system',
|
|
operationType: 'batch_update',
|
|
operationDescription: '系统批量更新',
|
|
targetId: 'SYSTEM',
|
|
targetName: '系统',
|
|
result: 'success',
|
|
req: mockReq
|
|
});
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { targetId: 'SYSTEM' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].operatorId).toBe('system');
|
|
expect(logs[0].operatorName).toBe('系统');
|
|
});
|
|
|
|
test('应该能够处理没有请求对象的场景', async () => {
|
|
await logOperation({
|
|
module: 'device',
|
|
operationType: 'update',
|
|
operationDescription: '无请求更新',
|
|
targetId: 'DEV_NO_REQ',
|
|
targetName: '无请求设备',
|
|
result: 'success',
|
|
req: null
|
|
});
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { targetId: 'DEV_NO_REQ' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].ipAddress).toBeNull();
|
|
expect(logs[0].userAgent).toBeNull();
|
|
});
|
|
|
|
test('应该能够记录失败的操作', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'user_fail', realName: '失败用户' },
|
|
headers: {}
|
|
};
|
|
|
|
await logOperation({
|
|
module: 'device',
|
|
operationType: 'delete',
|
|
operationDescription: '删除设备失败',
|
|
targetId: 'DEV_FAIL',
|
|
targetName: '失败设备',
|
|
result: 'failed',
|
|
req: mockReq,
|
|
metadata: { errorMessage: '设备不存在' }
|
|
});
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { targetId: 'DEV_FAIL' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].result).toBe('failed');
|
|
});
|
|
|
|
test('应该使用提供的 metadata', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'user_meta', realName: '元数据用户' },
|
|
headers: {}
|
|
};
|
|
|
|
const customMetadata = {
|
|
batchCount: 10,
|
|
source: 'import',
|
|
duration: 5000
|
|
};
|
|
|
|
await logOperation({
|
|
module: 'device',
|
|
operationType: 'batch_update',
|
|
operationDescription: '批量更新设备',
|
|
targetId: 'DEV_BATCH',
|
|
targetName: '批量设备',
|
|
result: 'success',
|
|
req: mockReq,
|
|
metadata: customMetadata
|
|
});
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { targetId: 'DEV_BATCH' }
|
|
});
|
|
|
|
expect(logs[0].metadata).toEqual(customMetadata);
|
|
});
|
|
});
|
|
|
|
describe('logDeviceOperation 设备操作日志', () => {
|
|
test('应该记录设备创建日志', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'user_dev', realName: '设备管理员' },
|
|
headers: { 'x-forwarded-for': '10.0.0.1' }
|
|
};
|
|
|
|
await logDeviceOperation(
|
|
'create',
|
|
'创建设备 测试服务器 (DEV001)',
|
|
{
|
|
targetId: 'DEV001',
|
|
targetName: '测试服务器',
|
|
afterState: { deviceId: 'DEV001', name: '测试服务器', status: 'running' },
|
|
req: mockReq
|
|
}
|
|
);
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { module: 'device', operationType: 'create' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].module).toBe('device');
|
|
expect(logs[0].operationType).toBe('create');
|
|
expect(logs[0].targetId).toBe('DEV001');
|
|
expect(logs[0].targetName).toBe('测试服务器');
|
|
});
|
|
|
|
test('应该记录设备更新日志并包含状态变更', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'user_upd', realName: '更新操作员' },
|
|
headers: {}
|
|
};
|
|
|
|
const beforeState = { name: '旧名称', status: 'offline' };
|
|
const afterState = { name: '新名称', status: 'running' };
|
|
|
|
await logDeviceOperation(
|
|
'update',
|
|
'更新设备 DEV002',
|
|
{
|
|
targetId: 'DEV002',
|
|
targetName: 'DEV002',
|
|
beforeState,
|
|
afterState,
|
|
req: mockReq
|
|
}
|
|
);
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { targetId: 'DEV002', operationType: 'update' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].beforeState).toEqual(beforeState);
|
|
expect(logs[0].afterState).toEqual(afterState);
|
|
});
|
|
|
|
test('应该记录设备删除日志', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'user_del', realName: '删除操作员' },
|
|
headers: {}
|
|
};
|
|
|
|
await logDeviceOperation(
|
|
'delete',
|
|
'删除设备 DEV003 (测试服务器)',
|
|
{
|
|
targetId: 'DEV003',
|
|
targetName: '测试服务器',
|
|
beforeState: { deviceId: 'DEV003', name: '测试服务器' },
|
|
req: mockReq
|
|
}
|
|
);
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { targetId: 'DEV003', operationType: 'delete' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
});
|
|
|
|
test('应该记录批量删除日志', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'user_batch', realName: '批量操作员' },
|
|
headers: {}
|
|
};
|
|
|
|
await logDeviceOperation(
|
|
'batch_delete',
|
|
'批量删除设备 3台 (DEV_A,DEV_B,DEV_C)',
|
|
{
|
|
targetId: 'DEV_A,DEV_B,DEV_C',
|
|
targetName: '3台设备',
|
|
beforeState: [
|
|
{ deviceId: 'DEV_A', name: '设备A' },
|
|
{ deviceId: 'DEV_B', name: '设备B' },
|
|
{ deviceId: 'DEV_C', name: '设备C' }
|
|
],
|
|
req: mockReq,
|
|
metadata: { count: 3 }
|
|
}
|
|
);
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { operationType: 'batch_delete' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].metadata.count).toBe(3);
|
|
});
|
|
|
|
test('应该记录状态变更日志', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'user_status', realName: '状态管理员' },
|
|
headers: {}
|
|
};
|
|
|
|
await logDeviceOperation(
|
|
'status_change',
|
|
'批量变更设备状态为"运行中"',
|
|
{
|
|
targetId: 'DEV_STATUS_1,DEV_STATUS_2',
|
|
targetName: '2台设备',
|
|
beforeState: [
|
|
{ deviceId: 'DEV_STATUS_1', status: 'offline' },
|
|
{ deviceId: 'DEV_STATUS_2', status: 'maintenance' }
|
|
],
|
|
afterState: [
|
|
{ deviceId: 'DEV_STATUS_1', status: 'running' },
|
|
{ deviceId: 'DEV_STATUS_2', status: 'running' }
|
|
],
|
|
req: mockReq,
|
|
metadata: { status: 'running', count: 2 }
|
|
}
|
|
);
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { operationType: 'status_change' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].metadata.status).toBe('running');
|
|
});
|
|
});
|
|
|
|
describe('logUserOperation 用户操作日志', () => {
|
|
test('应该记录用户创建日志', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'admin', realName: '系统管理员' },
|
|
headers: {}
|
|
};
|
|
|
|
await logUserOperation(
|
|
'create',
|
|
'创建用户 new_user',
|
|
{
|
|
targetId: 'user_new',
|
|
targetName: 'new_user',
|
|
afterState: { username: 'new_user', email: 'new@example.com' },
|
|
req: mockReq,
|
|
metadata: { roleIds: ['role_admin'] }
|
|
}
|
|
);
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { module: 'user', operationType: 'create' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].module).toBe('user');
|
|
expect(logs[0].targetName).toBe('new_user');
|
|
});
|
|
|
|
test('应该记录权限变更日志', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'admin', realName: '管理员' },
|
|
headers: {}
|
|
};
|
|
|
|
await logUserOperation(
|
|
'permission_change',
|
|
'变更用户 test_user 的角色',
|
|
{
|
|
targetId: 'user_test',
|
|
targetName: 'test_user',
|
|
beforeState: { roleIds: ['role_viewer'] },
|
|
afterState: { roleIds: ['role_admin'] },
|
|
req: mockReq,
|
|
metadata: { oldRoleIds: ['role_viewer'], newRoleIds: ['role_admin'] }
|
|
}
|
|
);
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { operationType: 'permission_change' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].metadata.oldRoleIds).toEqual(['role_viewer']);
|
|
expect(logs[0].metadata.newRoleIds).toEqual(['role_admin']);
|
|
});
|
|
|
|
test('应该记录用户删除日志', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'admin', realName: '管理员' },
|
|
headers: {}
|
|
};
|
|
|
|
await logUserOperation(
|
|
'delete',
|
|
'删除用户 deleted_user',
|
|
{
|
|
targetId: 'user_deleted',
|
|
targetName: 'deleted_user',
|
|
beforeState: { username: 'deleted_user', email: 'deleted@example.com' },
|
|
req: mockReq
|
|
}
|
|
);
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { module: 'user', operationType: 'delete' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('logRoleOperation 角色操作日志', () => {
|
|
test('应该记录角色创建日志', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'admin', realName: '管理员' },
|
|
headers: {}
|
|
};
|
|
|
|
await logRoleOperation(
|
|
'create',
|
|
'创建角色 测试角色',
|
|
{
|
|
targetId: 'role_test',
|
|
targetName: '测试角色',
|
|
afterState: { roleName: '测试角色', permissions: ['read', 'write'] },
|
|
req: mockReq,
|
|
metadata: { roleCode: 'test_role', permissions: ['read', 'write'] }
|
|
}
|
|
);
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { module: 'role', operationType: 'create' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].targetName).toBe('测试角色');
|
|
expect(logs[0].metadata.roleCode).toBe('test_role');
|
|
});
|
|
|
|
test('应该记录角色更新日志', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'admin', realName: '管理员' },
|
|
headers: {}
|
|
};
|
|
|
|
const beforeState = { roleName: '旧角色', permissions: ['read'] };
|
|
const afterState = { roleName: '新角色', permissions: ['read', 'write', 'delete'] };
|
|
|
|
await logRoleOperation(
|
|
'update',
|
|
'更新角色 角色A',
|
|
{
|
|
targetId: 'role_a',
|
|
targetName: '角色A',
|
|
beforeState,
|
|
afterState,
|
|
req: mockReq,
|
|
metadata: { oldRoleName: '旧角色', oldPermissions: ['read'] }
|
|
}
|
|
);
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { module: 'role', operationType: 'update' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].beforeState.roleName).toBe('旧角色');
|
|
expect(logs[0].afterState.roleName).toBe('新角色');
|
|
});
|
|
|
|
test('应该记录角色删除日志', async () => {
|
|
const mockReq = {
|
|
user: { userId: 'admin', realName: '管理员' },
|
|
headers: {}
|
|
};
|
|
|
|
await logRoleOperation(
|
|
'delete',
|
|
'删除角色 测试角色B',
|
|
{
|
|
targetId: 'role_b',
|
|
targetName: '测试角色B',
|
|
beforeState: { roleName: '测试角色B', userCount: 0 },
|
|
req: mockReq,
|
|
metadata: { userCount: 0 }
|
|
}
|
|
);
|
|
|
|
const logs = await OperationLog.findAll({
|
|
where: { module: 'role', operationType: 'delete' }
|
|
});
|
|
|
|
expect(logs.length).toBe(1);
|
|
expect(logs[0].metadata.userCount).toBe(0);
|
|
});
|
|
});
|
|
});
|