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

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

255 lines
8.0 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.
/**
* 管理员系统属性测试基础框架
*
* 功能描述:
* - 提供属性测试的基础工具和断言
* - 实现通用的测试数据生成器
* - 支持随机化测试和边界条件验证
*
* 属性测试原理:
* - 验证系统在各种输入条件下的通用正确性属性
* - 通过大量随机测试用例发现边界问题
* - 确保系统行为的一致性和可靠性
*
* 最近修改:
* - 2026-01-08: 注释规范优化 - 修正@author字段更新版本号和修改记录 (修改者: moyin)
* - 2026-01-08: 注释规范优化 - 为接口添加注释,完善文档说明 (修改者: moyin)
* - 2026-01-08: 功能新增 - 创建属性测试基础框架 (修改者: assistant)
*
* @author moyin
* @version 1.0.2
* @since 2026-01-08
* @lastModified 2026-01-08
*/
import { Logger } from '@nestjs/common';
import { UserStatus } from '../user_mgmt/user_status.enum';
/**
* 属性测试配置接口
*
* 功能描述:
* 定义属性测试的运行配置参数
*
* 使用场景:
* - 配置属性测试的迭代次数和超时时间
* - 设置随机种子以确保测试的可重现性
*/
export interface PropertyTestConfig {
iterations: number;
timeout: number;
seed?: number;
}
export const DEFAULT_PROPERTY_CONFIG: PropertyTestConfig = {
iterations: 100,
timeout: 30000,
seed: 12345
};
/**
* 属性测试生成器
*/
export class PropertyTestGenerators {
/**
* 生成随机用户数据
*/
static generateUser(seed?: number) {
const random = seed ? Math.sin(seed) * 10000 - Math.floor(Math.sin(seed) * 10000) : Math.random();
const id = Math.floor(random * 1000000);
return {
username: `testuser${id}`,
nickname: `Test User ${id}`,
email: `test${id}@example.com`,
phone: `138${String(id).padStart(8, '0').substring(0, 8)}`,
role: Math.floor(random * 10),
status: ['ACTIVE', 'INACTIVE', 'SUSPENDED'][Math.floor(random * 3)] as any,
avatar_url: `https://example.com/avatar${id}.jpg`,
github_id: `github${id}`
};
}
/**
* 生成随机用户档案数据
*/
static generateUserProfile(seed?: number) {
const random = seed ? Math.sin(seed) * 10000 - Math.floor(Math.sin(seed) * 10000) : Math.random();
const id = Math.floor(random * 1000000);
return {
user_id: String(id),
bio: `This is a test bio for user ${id}`,
resume_content: `Test resume content for user ${id}. This is a sample resume.`,
tags: JSON.stringify(['developer', 'tester']),
social_links: JSON.stringify({
github: `https://github.com/user${id}`,
linkedin: `https://linkedin.com/in/user${id}`
}),
skin_id: `skin${id}`,
current_map: ['plaza', 'forest', 'beach', 'mountain'][Math.floor(random * 4)],
pos_x: random * 1000,
pos_y: random * 1000,
status: Math.floor(random * 3)
};
}
/**
* 生成随机Zulip账号数据
*/
static generateZulipAccount(seed?: number) {
const random = seed ? Math.sin(seed) * 10000 - Math.floor(Math.sin(seed) * 10000) : Math.random();
const id = Math.floor(random * 1000000);
const statuses = ['active', 'inactive', 'suspended', 'error'] as const;
return {
gameUserId: String(id),
zulipUserId: Math.floor(random * 999999) + 1,
zulipEmail: `zulip${id}@example.com`,
zulipFullName: `Zulip User ${id}`,
zulipApiKeyEncrypted: `encrypted_key_${id}`,
status: statuses[Math.floor(random * 4)]
};
}
/**
* 生成随机分页参数
*/
static generatePaginationParams(seed?: number) {
const random = seed ? Math.sin(seed) * 10000 - Math.floor(Math.sin(seed) * 10000) : Math.random();
return {
limit: Math.floor(random * 100) + 1,
offset: Math.floor(random * 1000)
};
}
/**
* 生成边界值测试数据
*/
static generateBoundaryValues() {
return {
limits: [0, 1, 50, 100, 101, 999, 1000],
offsets: [0, 1, 100, 999, 1000, 9999],
strings: ['', 'a', 'x'.repeat(50), 'x'.repeat(255), 'x'.repeat(256)],
numbers: [-1, 0, 1, 999, 1000, 9999, 99999]
};
}
}
/**
* 属性测试断言工具
*/
export class PropertyTestAssertions {
/**
* 验证API响应格式一致性
*/
static assertApiResponseFormat(response: any, shouldHaveData: boolean = true) {
expect(response).toHaveProperty('success');
expect(response).toHaveProperty('message');
expect(response).toHaveProperty('timestamp');
expect(response).toHaveProperty('request_id');
expect(typeof response.success).toBe('boolean');
expect(typeof response.message).toBe('string');
expect(typeof response.timestamp).toBe('string');
expect(typeof response.request_id).toBe('string');
if (shouldHaveData && response.success) {
expect(response).toHaveProperty('data');
}
if (!response.success) {
expect(response).toHaveProperty('error_code');
expect(typeof response.error_code).toBe('string');
}
}
/**
* 验证列表响应格式
*/
static assertListResponseFormat(response: any) {
this.assertApiResponseFormat(response, true);
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');
}
/**
* 验证分页逻辑正确性
*/
static assertPaginationLogic(response: any, requestedLimit: number, requestedOffset: number) {
this.assertListResponseFormat(response);
const { items, total, limit, offset, has_more } = response.data;
// 验证分页参数
expect(limit).toBeLessThanOrEqual(100); // 最大限制
expect(offset).toBeGreaterThanOrEqual(0);
// 验证has_more逻辑
const expectedHasMore = offset + items.length < total;
expect(has_more).toBe(expectedHasMore);
// 验证返回项目数量
expect(items.length).toBeLessThanOrEqual(limit);
}
/**
* 验证CRUD操作一致性
*/
static assertCrudConsistency(createResponse: any, readResponse: any, updateResponse: any) {
// 创建和读取的数据应该一致
expect(createResponse.success).toBe(true);
expect(readResponse.success).toBe(true);
expect(createResponse.data.id).toBe(readResponse.data.id);
// 更新后的数据应该反映变更
expect(updateResponse.success).toBe(true);
expect(updateResponse.data.id).toBe(createResponse.data.id);
}
}
/**
* 属性测试运行器
*/
export class PropertyTestRunner {
static async runPropertyTest<T>(
testName: string,
generator: () => T,
testFunction: (input: T) => Promise<void>,
config: PropertyTestConfig = DEFAULT_PROPERTY_CONFIG
): Promise<void> {
const logger = new Logger('PropertyTestRunner');
logger.log(`Running property test: ${testName} with ${config.iterations} iterations`);
const failures: Array<{ iteration: number; input: T; error: any }> = [];
for (let i = 0; i < config.iterations; i++) {
try {
const input = generator();
await testFunction(input);
} catch (error) {
failures.push({
iteration: i,
input: generator(), // 重新生成用于错误报告
error
});
}
}
if (failures.length > 0) {
const failureRate = (failures.length / config.iterations) * 100;
logger.error(`Property test failed: ${failures.length}/${config.iterations} iterations failed (${failureRate.toFixed(2)}%)`);
logger.error('First failure:', failures[0]);
throw new Error(`Property test "${testName}" failed with ${failures.length} failures`);
}
logger.log(`Property test "${testName}" passed all ${config.iterations} iterations`);
}
}