forked from datawhale/whale-town-end
feat:实现管理员系统核心功能
- 添加管理员数据库管理控制器和服务 - 实现管理员操作日志记录系统 - 添加数据库异常处理过滤器 - 完善管理员权限验证和响应格式 - 添加全面的属性测试覆盖
This commit is contained in:
435
src/business/admin/admin_database.integration.spec.ts
Normal file
435
src/business/admin/admin_database.integration.spec.ts
Normal file
@@ -0,0 +1,435 @@
|
||||
/**
|
||||
* 管理员数据库管理集成测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试管理员数据库管理的完整功能
|
||||
* - 验证CRUD操作的正确性
|
||||
* - 测试权限控制和错误处理
|
||||
* - 验证响应格式的一致性
|
||||
*
|
||||
* 测试覆盖:
|
||||
* - 用户管理功能测试
|
||||
* - 用户档案管理功能测试
|
||||
* - Zulip账号关联管理功能测试
|
||||
* - 批量操作功能测试
|
||||
* - 错误处理和边界条件测试
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-08: 注释规范优化 - 修正@author字段,更新版本号和修改记录 (修改者: moyin)
|
||||
* - 2026-01-08: 功能新增 - 创建管理员数据库管理集成测试 (修改者: assistant)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2026-01-08
|
||||
* @lastModified 2026-01-08
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AdminDatabaseController } from '../controllers/admin_database.controller';
|
||||
import { DatabaseManagementService } from '../services/database_management.service';
|
||||
import { AdminOperationLogService } from '../services/admin_operation_log.service';
|
||||
import { AdminOperationLogInterceptor } from '../admin_operation_log.interceptor';
|
||||
import { AdminDatabaseExceptionFilter } from '../admin_database_exception.filter';
|
||||
import { AdminGuard } from '../admin.guard';
|
||||
import { UserStatus } from '../../../core/db/users/user_status.enum';
|
||||
|
||||
describe('Admin Database Management Integration Tests', () => {
|
||||
let app: INestApplication;
|
||||
let module: TestingModule;
|
||||
let controller: AdminDatabaseController;
|
||||
let service: DatabaseManagementService;
|
||||
|
||||
// 测试数据
|
||||
const testUser = {
|
||||
username: 'admin-test-user',
|
||||
nickname: '管理员测试用户',
|
||||
email: 'admin-test@example.com',
|
||||
role: 1,
|
||||
status: UserStatus.ACTIVE
|
||||
};
|
||||
|
||||
const testProfile = {
|
||||
user_id: '1',
|
||||
bio: '管理员测试档案',
|
||||
current_map: 'test-plaza',
|
||||
pos_x: 100.5,
|
||||
pos_y: 200.3,
|
||||
status: 1
|
||||
};
|
||||
|
||||
const testZulipAccount = {
|
||||
gameUserId: '1',
|
||||
zulipUserId: 12345,
|
||||
zulipEmail: 'test@zulip.com',
|
||||
zulipFullName: '测试用户',
|
||||
zulipApiKeyEncrypted: 'encrypted_test_key',
|
||||
status: 'active'
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
module = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: ['.env.test', '.env']
|
||||
})
|
||||
],
|
||||
controllers: [AdminDatabaseController],
|
||||
providers: [
|
||||
DatabaseManagementService,
|
||||
// Mock AdminOperationLogService for testing
|
||||
{
|
||||
provide: AdminOperationLogService,
|
||||
useValue: {
|
||||
createLog: jest.fn().mockResolvedValue({}),
|
||||
queryLogs: jest.fn().mockResolvedValue({ logs: [], total: 0 }),
|
||||
getLogById: jest.fn().mockResolvedValue(null),
|
||||
getStatistics: jest.fn().mockResolvedValue({}),
|
||||
cleanupExpiredLogs: jest.fn().mockResolvedValue(0),
|
||||
getAdminOperationHistory: jest.fn().mockResolvedValue([]),
|
||||
getSensitiveOperations: jest.fn().mockResolvedValue({ logs: [], total: 0 })
|
||||
}
|
||||
},
|
||||
// Mock AdminOperationLogInterceptor
|
||||
{
|
||||
provide: AdminOperationLogInterceptor,
|
||||
useValue: {
|
||||
intercept: jest.fn().mockImplementation((context, next) => next.handle())
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: 'UsersService',
|
||||
useValue: {
|
||||
findAll: jest.fn().mockResolvedValue([]),
|
||||
findOne: jest.fn().mockResolvedValue({ ...testUser, id: BigInt(1) }),
|
||||
create: jest.fn().mockResolvedValue({ ...testUser, id: BigInt(1) }),
|
||||
update: jest.fn().mockResolvedValue({ ...testUser, id: BigInt(1) }),
|
||||
remove: jest.fn().mockResolvedValue(undefined),
|
||||
search: jest.fn().mockResolvedValue([]),
|
||||
count: jest.fn().mockResolvedValue(0)
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: 'IUserProfilesService',
|
||||
useValue: {
|
||||
findAll: jest.fn().mockResolvedValue([]),
|
||||
findOne: jest.fn().mockResolvedValue({ ...testProfile, id: BigInt(1) }),
|
||||
create: jest.fn().mockResolvedValue({ ...testProfile, id: BigInt(1) }),
|
||||
update: jest.fn().mockResolvedValue({ ...testProfile, id: BigInt(1) }),
|
||||
remove: jest.fn().mockResolvedValue(undefined),
|
||||
findByMap: jest.fn().mockResolvedValue([]),
|
||||
count: jest.fn().mockResolvedValue(0)
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: 'ZulipAccountsService',
|
||||
useValue: {
|
||||
findMany: jest.fn().mockResolvedValue({ accounts: [] }),
|
||||
findById: jest.fn().mockResolvedValue(testZulipAccount),
|
||||
create: jest.fn().mockResolvedValue({ ...testZulipAccount, id: '1' }),
|
||||
update: jest.fn().mockResolvedValue({ ...testZulipAccount, id: '1' }),
|
||||
delete: jest.fn().mockResolvedValue(undefined),
|
||||
getStatusStatistics: jest.fn().mockResolvedValue({
|
||||
active: 0,
|
||||
inactive: 0,
|
||||
suspended: 0,
|
||||
error: 0,
|
||||
total: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
.overrideGuard(AdminGuard)
|
||||
.useValue({ canActivate: () => true })
|
||||
.compile();
|
||||
|
||||
app = module.createNestApplication();
|
||||
app.useGlobalFilters(new AdminDatabaseExceptionFilter());
|
||||
await app.init();
|
||||
|
||||
controller = module.get<AdminDatabaseController>(AdminDatabaseController);
|
||||
service = module.get<DatabaseManagementService>(DatabaseManagementService);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe('用户管理功能测试', () => {
|
||||
it('应该成功获取用户列表', async () => {
|
||||
const result = await controller.getUserList(20, 0);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.items).toBeInstanceOf(Array);
|
||||
expect(result.data.total).toBeDefined();
|
||||
expect(result.data.limit).toBe(20);
|
||||
expect(result.data.offset).toBe(0);
|
||||
expect(result.message).toBe('用户列表获取成功');
|
||||
});
|
||||
|
||||
it('应该成功获取用户详情', async () => {
|
||||
const result = await controller.getUserById('1');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.username).toBe(testUser.username);
|
||||
expect(result.message).toBe('用户详情获取成功');
|
||||
});
|
||||
|
||||
it('应该成功创建用户', async () => {
|
||||
const result = await controller.createUser(testUser);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.username).toBe(testUser.username);
|
||||
expect(result.message).toBe('用户创建成功');
|
||||
});
|
||||
|
||||
it('应该成功更新用户', async () => {
|
||||
const updateData = { nickname: '更新后的昵称' };
|
||||
const result = await controller.updateUser('1', updateData);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.message).toBe('用户更新成功');
|
||||
});
|
||||
|
||||
it('应该成功删除用户', async () => {
|
||||
const result = await controller.deleteUser('1');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data.deleted).toBe(true);
|
||||
expect(result.message).toBe('用户删除成功');
|
||||
});
|
||||
|
||||
it('应该成功搜索用户', async () => {
|
||||
const result = await controller.searchUsers('admin', 20);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.items).toBeInstanceOf(Array);
|
||||
expect(result.message).toBe('用户搜索成功');
|
||||
});
|
||||
});
|
||||
|
||||
describe('用户档案管理功能测试', () => {
|
||||
it('应该成功获取用户档案列表', async () => {
|
||||
const result = await controller.getUserProfileList(20, 0);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.items).toBeInstanceOf(Array);
|
||||
expect(result.message).toBe('用户档案列表获取成功');
|
||||
});
|
||||
|
||||
it('应该成功获取用户档案详情', async () => {
|
||||
const result = await controller.getUserProfileById('1');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.user_id).toBe(testProfile.user_id);
|
||||
expect(result.message).toBe('用户档案详情获取成功');
|
||||
});
|
||||
|
||||
it('应该成功创建用户档案', async () => {
|
||||
const result = await controller.createUserProfile(testProfile);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.user_id).toBe(testProfile.user_id);
|
||||
expect(result.message).toBe('用户档案创建成功');
|
||||
});
|
||||
|
||||
it('应该成功更新用户档案', async () => {
|
||||
const updateData = { bio: '更新后的简介' };
|
||||
const result = await controller.updateUserProfile('1', updateData);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.message).toBe('用户档案更新成功');
|
||||
});
|
||||
|
||||
it('应该成功删除用户档案', async () => {
|
||||
const result = await controller.deleteUserProfile('1');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data.deleted).toBe(true);
|
||||
expect(result.message).toBe('用户档案删除成功');
|
||||
});
|
||||
|
||||
it('应该成功根据地图获取用户档案', async () => {
|
||||
const result = await controller.getUserProfilesByMap('plaza', 20, 0);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.items).toBeInstanceOf(Array);
|
||||
expect(result.message).toBe('地图 plaza 的用户档案获取成功');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Zulip账号关联管理功能测试', () => {
|
||||
it('应该成功获取Zulip账号关联列表', async () => {
|
||||
const result = await controller.getZulipAccountList(20, 0);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.items).toBeInstanceOf(Array);
|
||||
expect(result.message).toBe('Zulip账号关联列表获取成功');
|
||||
});
|
||||
|
||||
it('应该成功获取Zulip账号关联详情', async () => {
|
||||
const result = await controller.getZulipAccountById('1');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.gameUserId).toBe(testZulipAccount.gameUserId);
|
||||
expect(result.message).toBe('Zulip账号关联详情获取成功');
|
||||
});
|
||||
|
||||
it('应该成功创建Zulip账号关联', async () => {
|
||||
const result = await controller.createZulipAccount(testZulipAccount);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.gameUserId).toBe(testZulipAccount.gameUserId);
|
||||
expect(result.message).toBe('Zulip账号关联创建成功');
|
||||
});
|
||||
|
||||
it('应该成功更新Zulip账号关联', async () => {
|
||||
const updateData = { status: 'inactive' };
|
||||
const result = await controller.updateZulipAccount('1', updateData);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.message).toBe('Zulip账号关联更新成功');
|
||||
});
|
||||
|
||||
it('应该成功删除Zulip账号关联', async () => {
|
||||
const result = await controller.deleteZulipAccount('1');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data.deleted).toBe(true);
|
||||
expect(result.message).toBe('Zulip账号关联删除成功');
|
||||
});
|
||||
|
||||
it('应该成功批量更新Zulip账号状态', async () => {
|
||||
const batchData = {
|
||||
ids: ['1', '2', '3'],
|
||||
status: 'active' as 'active' | 'inactive' | 'suspended' | 'error',
|
||||
reason: '批量激活测试'
|
||||
};
|
||||
const result = await controller.batchUpdateZulipAccountStatus(batchData);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.total).toBe(3);
|
||||
expect(result.message).toContain('批量更新完成');
|
||||
});
|
||||
|
||||
it('应该成功获取Zulip账号关联统计', async () => {
|
||||
const result = await controller.getZulipAccountStatistics();
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.total).toBeDefined();
|
||||
expect(result.message).toBe('Zulip账号关联统计获取成功');
|
||||
});
|
||||
});
|
||||
|
||||
describe('系统功能测试', () => {
|
||||
it('应该成功进行健康检查', async () => {
|
||||
const result = await controller.healthCheck();
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data).toBeDefined();
|
||||
expect(result.data.status).toBe('healthy');
|
||||
expect(result.data.services).toBeDefined();
|
||||
expect(result.message).toBe('数据库管理系统运行正常');
|
||||
});
|
||||
});
|
||||
|
||||
describe('响应格式一致性测试', () => {
|
||||
it('所有成功响应应该有统一的格式', async () => {
|
||||
const responses = [
|
||||
await controller.getUserList(20, 0),
|
||||
await controller.getUserById('1'),
|
||||
await controller.getUserProfileList(20, 0),
|
||||
await controller.getZulipAccountList(20, 0),
|
||||
await controller.healthCheck()
|
||||
];
|
||||
|
||||
responses.forEach(response => {
|
||||
expect(response).toHaveProperty('success');
|
||||
expect(response).toHaveProperty('message');
|
||||
expect(response).toHaveProperty('data');
|
||||
expect(response).toHaveProperty('timestamp');
|
||||
expect(response).toHaveProperty('request_id');
|
||||
expect(response.success).toBe(true);
|
||||
expect(typeof response.message).toBe('string');
|
||||
expect(typeof response.timestamp).toBe('string');
|
||||
expect(typeof response.request_id).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
it('列表响应应该有分页信息', async () => {
|
||||
const listResponses = [
|
||||
await controller.getUserList(20, 0),
|
||||
await controller.getUserProfileList(20, 0),
|
||||
await controller.getZulipAccountList(20, 0)
|
||||
];
|
||||
|
||||
listResponses.forEach(response => {
|
||||
expect(response.data).toHaveProperty('items');
|
||||
expect(response.data).toHaveProperty('total');
|
||||
expect(response.data).toHaveProperty('limit');
|
||||
expect(response.data).toHaveProperty('offset');
|
||||
expect(response.data).toHaveProperty('has_more');
|
||||
expect(Array.isArray(response.data.items)).toBe(true);
|
||||
expect(typeof response.data.total).toBe('number');
|
||||
expect(typeof response.data.limit).toBe('number');
|
||||
expect(typeof response.data.offset).toBe('number');
|
||||
expect(typeof response.data.has_more).toBe('boolean');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('参数验证测试', () => {
|
||||
it('应该正确处理分页参数限制', async () => {
|
||||
// 测试超过最大限制的情况
|
||||
const result = await controller.getUserList(200, 0);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('应该正确处理搜索参数限制', async () => {
|
||||
const result = await controller.searchUsers('test', 100);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user