Files
whale-town-end/src/business/admin/permission_verification.property.spec.ts
moyin 5f662ef091 feat: 完善管理员系统和用户管理模块
- 更新管理员控制器和数据库管理功能
- 完善管理员操作日志系统
- 添加全面的属性测试覆盖
- 优化用户管理和用户档案服务
- 更新代码检查规范文档

功能改进:
- 增强管理员权限验证
- 完善操作日志记录
- 优化数据库管理接口
- 提升系统安全性和可维护性
2026-01-09 17:05:08 +08:00

658 lines
21 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 权限验证属性测试
*
* Property 10: 权限验证严格性
* Property 15: 并发请求限流
*
* Validates: Requirements 5.1, 8.4
*
* 测试目标:
* - 验证权限验证机制的严格性和一致性
* - 确保并发请求限流保护有效
* - 验证权限边界和异常情况处理
*
* 最近修改:
* - 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 { AdminDatabaseController } from './admin_database.controller';
import { DatabaseManagementService } from './database_management.service';
import { AdminOperationLogService } from './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 '../user_mgmt/user_status.enum';
import {
PropertyTestRunner,
PropertyTestGenerators,
PropertyTestAssertions,
DEFAULT_PROPERTY_CONFIG
} from './admin_property_test.base';
describe('Property Test: 权限验证功能', () => {
let app: INestApplication;
let module: TestingModule;
let controller: AdminDatabaseController;
let mockAdminGuard: any;
let requestCount = 0;
let concurrentRequests = new Set<string>();
beforeAll(async () => {
requestCount = 0;
concurrentRequests.clear();
mockAdminGuard = {
canActivate: jest.fn().mockImplementation((context) => {
const request = context.switchToHttp().getRequest();
const requestId = request.headers['x-request-id'] || `req_${Date.now()}_${Math.random()}`;
// 模拟权限验证逻辑
const authHeader = request.headers.authorization;
const adminRole = request.headers['x-admin-role'];
const adminId = request.headers['x-admin-id'];
// 并发请求跟踪
if (concurrentRequests.has(requestId)) {
return false; // 重复请求
}
concurrentRequests.add(requestId);
// 模拟请求完成后清理
setTimeout(() => {
concurrentRequests.delete(requestId);
}, 100);
requestCount++;
// 权限验证规则
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return false;
}
if (!adminRole || !['super_admin', 'admin', 'moderator'].includes(adminRole)) {
return false;
}
if (!adminId || adminId.length < 3) {
return false;
}
// 模拟频率限制每秒最多10个请求
const now = Date.now();
const windowStart = Math.floor(now / 1000) * 1000;
const recentRequests = Array.from(concurrentRequests).filter(id =>
id.startsWith(`req_${windowStart}`)
);
if (recentRequests.length > 10) {
return false; // 超过频率限制
}
return true;
})
};
module = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['.env.test', '.env']
})
],
controllers: [AdminDatabaseController],
providers: [
DatabaseManagementService,
{
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 })
}
},
{
provide: AdminOperationLogInterceptor,
useValue: {
intercept: jest.fn().mockImplementation((context, next) => next.handle())
}
},
{
provide: 'UsersService',
useValue: {
findAll: jest.fn().mockResolvedValue([]),
findOne: jest.fn().mockImplementation(() => {
const user = PropertyTestGenerators.generateUser();
return Promise.resolve({ ...user, id: BigInt(1) });
}),
create: jest.fn().mockImplementation((userData) => {
return Promise.resolve({ ...userData, id: BigInt(1) });
}),
update: jest.fn().mockImplementation((id, updateData) => {
const user = PropertyTestGenerators.generateUser();
return Promise.resolve({ ...user, ...updateData, id });
}),
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({ id: BigInt(1) }),
create: jest.fn().mockResolvedValue({ id: BigInt(1) }),
update: jest.fn().mockResolvedValue({ 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({ id: '1' }),
create: jest.fn().mockResolvedValue({ id: '1' }),
update: jest.fn().mockResolvedValue({ id: '1' }),
delete: jest.fn().mockResolvedValue(undefined),
getStatusStatistics: jest.fn().mockResolvedValue({
active: 0, inactive: 0, suspended: 0, error: 0, total: 0
})
}
}
]
})
.overrideGuard(AdminGuard)
.useValue(mockAdminGuard)
.compile();
app = module.createNestApplication();
app.useGlobalFilters(new AdminDatabaseExceptionFilter());
await app.init();
controller = module.get<AdminDatabaseController>(AdminDatabaseController);
});
afterAll(async () => {
await app.close();
});
beforeEach(() => {
requestCount = 0;
concurrentRequests.clear();
mockAdminGuard.canActivate.mockClear();
});
describe('Property 10: 权限验证严格性', () => {
it('有效的管理员凭证应该通过验证', async () => {
await PropertyTestRunner.runPropertyTest(
'有效凭证权限验证',
() => {
const roles = ['super_admin', 'admin', 'moderator'];
return {
authToken: `Bearer token_${Math.random().toString(36).substring(7)}`,
adminRole: roles[Math.floor(Math.random() * roles.length)],
adminId: `admin_${Math.floor(Math.random() * 1000) + 100}`
};
},
async ({ authToken, adminRole, adminId }) => {
// 模拟设置请求头
const mockRequest = {
headers: {
authorization: authToken,
'x-admin-role': adminRole,
'x-admin-id': adminId,
'x-request-id': `req_${Date.now()}_${Math.random()}`
}
};
const mockContext = {
switchToHttp: () => ({
getRequest: () => mockRequest
})
};
const canActivate = mockAdminGuard.canActivate(mockContext);
expect(canActivate).toBe(true);
},
{ ...DEFAULT_PROPERTY_CONFIG, iterations: 30 }
);
});
it('无效的认证令牌应该被拒绝', async () => {
await PropertyTestRunner.runPropertyTest(
'无效令牌权限拒绝',
() => {
const invalidTokens = [
'', // 空令牌
'InvalidToken', // 不是Bearer格式
'Bearer', // 只有Bearer前缀
'Basic dGVzdA==', // 错误的认证类型
null,
undefined
];
return {
authToken: invalidTokens[Math.floor(Math.random() * invalidTokens.length)],
adminRole: 'admin',
adminId: 'admin_123'
};
},
async ({ authToken, adminRole, adminId }) => {
const mockRequest = {
headers: {
authorization: authToken,
'x-admin-role': adminRole,
'x-admin-id': adminId,
'x-request-id': `req_${Date.now()}_${Math.random()}`
}
};
const mockContext = {
switchToHttp: () => ({
getRequest: () => mockRequest
})
};
const canActivate = mockAdminGuard.canActivate(mockContext);
expect(canActivate).toBe(false);
},
{ ...DEFAULT_PROPERTY_CONFIG, iterations: 25 }
);
});
it('无效的管理员角色应该被拒绝', async () => {
await PropertyTestRunner.runPropertyTest(
'无效角色权限拒绝',
() => {
const invalidRoles = [
'user', // 普通用户角色
'guest', // 访客角色
'invalid_role', // 无效角色
'', // 空角色
'ADMIN', // 大小写错误
null,
undefined
];
return {
authToken: 'Bearer valid_token_123',
adminRole: invalidRoles[Math.floor(Math.random() * invalidRoles.length)],
adminId: 'admin_123'
};
},
async ({ authToken, adminRole, adminId }) => {
const mockRequest = {
headers: {
authorization: authToken,
'x-admin-role': adminRole,
'x-admin-id': adminId,
'x-request-id': `req_${Date.now()}_${Math.random()}`
}
};
const mockContext = {
switchToHttp: () => ({
getRequest: () => mockRequest
})
};
const canActivate = mockAdminGuard.canActivate(mockContext);
expect(canActivate).toBe(false);
},
{ ...DEFAULT_PROPERTY_CONFIG, iterations: 25 }
);
});
it('无效的管理员ID应该被拒绝', async () => {
await PropertyTestRunner.runPropertyTest(
'无效管理员ID权限拒绝',
() => {
const invalidIds = [
'', // 空ID
'a', // 太短的ID
'ab', // 太短的ID
null,
undefined,
' ', // 只有空格
'id with spaces' // 包含空格
];
return {
authToken: 'Bearer valid_token_123',
adminRole: 'admin',
adminId: invalidIds[Math.floor(Math.random() * invalidIds.length)]
};
},
async ({ authToken, adminRole, adminId }) => {
const mockRequest = {
headers: {
authorization: authToken,
'x-admin-role': adminRole,
'x-admin-id': adminId,
'x-request-id': `req_${Date.now()}_${Math.random()}`
}
};
const mockContext = {
switchToHttp: () => ({
getRequest: () => mockRequest
})
};
const canActivate = mockAdminGuard.canActivate(mockContext);
expect(canActivate).toBe(false);
},
{ ...DEFAULT_PROPERTY_CONFIG, iterations: 25 }
);
});
it('权限验证应该在所有端点中一致执行', async () => {
await PropertyTestRunner.runPropertyTest(
'权限验证一致性',
() => ({
validAuth: {
authToken: 'Bearer valid_token_123',
adminRole: 'admin',
adminId: 'admin_123'
},
invalidAuth: {
authToken: 'InvalidToken',
adminRole: 'admin',
adminId: 'admin_123'
}
}),
async ({ validAuth, invalidAuth }) => {
// 测试有效权限
const validRequest = {
headers: {
authorization: validAuth.authToken,
'x-admin-role': validAuth.adminRole,
'x-admin-id': validAuth.adminId,
'x-request-id': `req_${Date.now()}_${Math.random()}`
}
};
const validContext = {
switchToHttp: () => ({
getRequest: () => validRequest
})
};
expect(mockAdminGuard.canActivate(validContext)).toBe(true);
// 测试无效权限
const invalidRequest = {
headers: {
authorization: invalidAuth.authToken,
'x-admin-role': invalidAuth.adminRole,
'x-admin-id': invalidAuth.adminId,
'x-request-id': `req_${Date.now()}_${Math.random()}`
}
};
const invalidContext = {
switchToHttp: () => ({
getRequest: () => invalidRequest
})
};
expect(mockAdminGuard.canActivate(invalidContext)).toBe(false);
},
{ ...DEFAULT_PROPERTY_CONFIG, iterations: 20 }
);
});
});
describe('Property 15: 并发请求限流', () => {
it('正常频率的请求应该被允许', async () => {
await PropertyTestRunner.runPropertyTest(
'正常频率请求允许',
() => ({
requestCount: Math.floor(Math.random() * 5) + 1 // 1-5个请求
}),
async ({ requestCount }) => {
const results = [];
for (let i = 0; i < requestCount; i++) {
const mockRequest = {
headers: {
authorization: 'Bearer valid_token_123',
'x-admin-role': 'admin',
'x-admin-id': 'admin_123',
'x-request-id': `req_${Date.now()}_${i}_${Math.random()}`
}
};
const mockContext = {
switchToHttp: () => ({
getRequest: () => mockRequest
})
};
const result = mockAdminGuard.canActivate(mockContext);
results.push(result);
// 小延迟避免时间戳冲突
await new Promise(resolve => setTimeout(resolve, 10));
}
// 正常频率的请求都应该被允许
results.forEach(result => {
expect(result).toBe(true);
});
},
{ ...DEFAULT_PROPERTY_CONFIG, iterations: 20 }
);
});
it('重复的请求ID应该被拒绝', async () => {
await PropertyTestRunner.runPropertyTest(
'重复请求ID拒绝',
() => ({
requestId: `req_${Date.now()}_${Math.random()}`
}),
async ({ requestId }) => {
const mockRequest1 = {
headers: {
authorization: 'Bearer valid_token_123',
'x-admin-role': 'admin',
'x-admin-id': 'admin_123',
'x-request-id': requestId
}
};
const mockRequest2 = {
headers: {
authorization: 'Bearer valid_token_456',
'x-admin-role': 'admin',
'x-admin-id': 'admin_456',
'x-request-id': requestId // 相同的请求ID
}
};
const mockContext1 = {
switchToHttp: () => ({
getRequest: () => mockRequest1
})
};
const mockContext2 = {
switchToHttp: () => ({
getRequest: () => mockRequest2
})
};
// 第一个请求应该成功
const result1 = mockAdminGuard.canActivate(mockContext1);
expect(result1).toBe(true);
// 第二个请求重复ID应该被拒绝
const result2 = mockAdminGuard.canActivate(mockContext2);
expect(result2).toBe(false);
},
{ ...DEFAULT_PROPERTY_CONFIG, iterations: 15 }
);
});
it('并发请求数量应该被正确跟踪', async () => {
await PropertyTestRunner.runPropertyTest(
'并发请求跟踪',
() => ({
concurrentCount: Math.floor(Math.random() * 8) + 3 // 3-10个并发请求
}),
async ({ concurrentCount }) => {
const promises = [];
const results = [];
// 创建并发请求
for (let i = 0; i < concurrentCount; i++) {
const promise = new Promise((resolve) => {
const mockRequest = {
headers: {
authorization: 'Bearer valid_token_123',
'x-admin-role': 'admin',
'x-admin-id': `admin_${i}`,
'x-request-id': `req_${Date.now()}_${i}_${Math.random()}`
}
};
const mockContext = {
switchToHttp: () => ({
getRequest: () => mockRequest
})
};
const result = mockAdminGuard.canActivate(mockContext);
results.push(result);
resolve(result);
});
promises.push(promise);
}
// 等待所有请求完成
await Promise.all(promises);
// 验证并发控制
const successCount = results.filter(r => r === true).length;
const failureCount = results.filter(r => r === false).length;
expect(successCount + failureCount).toBe(concurrentCount);
// 如果并发数超过限制,应该有一些请求被拒绝
if (concurrentCount > 10) {
expect(failureCount).toBeGreaterThan(0);
}
},
{ ...DEFAULT_PROPERTY_CONFIG, iterations: 15 }
);
});
it('请求完成后应该释放并发槽位', async () => {
await PropertyTestRunner.runPropertyTest(
'并发槽位释放',
() => ({}),
async () => {
const initialConcurrentSize = concurrentRequests.size;
// 创建一个请求
const mockRequest = {
headers: {
authorization: 'Bearer valid_token_123',
'x-admin-role': 'admin',
'x-admin-id': 'admin_123',
'x-request-id': `req_${Date.now()}_${Math.random()}`
}
};
const mockContext = {
switchToHttp: () => ({
getRequest: () => mockRequest
})
};
const result = mockAdminGuard.canActivate(mockContext);
expect(result).toBe(true);
// 验证并发计数增加
expect(concurrentRequests.size).toBe(initialConcurrentSize + 1);
// 等待请求完成模拟的100ms超时
await new Promise(resolve => setTimeout(resolve, 150));
// 验证并发计数恢复
expect(concurrentRequests.size).toBe(initialConcurrentSize);
},
{ ...DEFAULT_PROPERTY_CONFIG, iterations: 10 }
);
});
it('不同时间窗口的请求应该独立计算', async () => {
await PropertyTestRunner.runPropertyTest(
'时间窗口独立计算',
() => ({}),
async () => {
const timestamp1 = Date.now();
const timestamp2 = timestamp1 + 1100; // 下一秒
// 第一个时间窗口的请求
const mockRequest1 = {
headers: {
authorization: 'Bearer valid_token_123',
'x-admin-role': 'admin',
'x-admin-id': 'admin_123',
'x-request-id': `req_${timestamp1}_1`
}
};
const mockContext1 = {
switchToHttp: () => ({
getRequest: () => mockRequest1
})
};
const result1 = mockAdminGuard.canActivate(mockContext1);
expect(result1).toBe(true);
// 模拟时间推进
await new Promise(resolve => setTimeout(resolve, 1100));
// 第二个时间窗口的请求
const mockRequest2 = {
headers: {
authorization: 'Bearer valid_token_123',
'x-admin-role': 'admin',
'x-admin-id': 'admin_123',
'x-request-id': `req_${timestamp2}_1`
}
};
const mockContext2 = {
switchToHttp: () => ({
getRequest: () => mockRequest2
})
};
const result2 = mockAdminGuard.canActivate(mockContext2);
expect(result2).toBe(true);
},
{ ...DEFAULT_PROPERTY_CONFIG, iterations: 5 }
);
});
});
});