/** * 权限验证属性测试 * * 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(); 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); }); 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 } ); }); }); });