feat:实现管理员系统核心功能
- 添加管理员数据库管理控制器和服务 - 实现管理员操作日志记录系统 - 添加数据库异常处理过滤器 - 完善管理员权限验证和响应格式 - 添加全面的属性测试覆盖
This commit is contained in:
258
src/business/admin/admin_property_test.base.ts
Normal file
258
src/business/admin/admin_property_test.base.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* 管理员系统属性测试基础框架
|
||||
*
|
||||
* 功能描述:
|
||||
* - 提供属性测试的基础工具和断言
|
||||
* - 实现通用的测试数据生成器
|
||||
* - 支持随机化测试和边界条件验证
|
||||
*
|
||||
* 属性测试原理:
|
||||
* - 验证系统在各种输入条件下的通用正确性属性
|
||||
* - 通过大量随机测试用例发现边界问题
|
||||
* - 确保系统行为的一致性和可靠性
|
||||
*
|
||||
* 最近修改:
|
||||
* - 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 { faker } from '@faker-js/faker';
|
||||
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 {
|
||||
private static setupFaker(seed?: number) {
|
||||
if (seed) {
|
||||
faker.seed(seed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机用户数据
|
||||
*/
|
||||
static generateUser(seed?: number) {
|
||||
this.setupFaker(seed);
|
||||
return {
|
||||
username: faker.internet.username(),
|
||||
nickname: faker.person.fullName(),
|
||||
email: faker.internet.email(),
|
||||
phone: faker.phone.number(),
|
||||
role: faker.number.int({ min: 0, max: 9 }),
|
||||
status: faker.helpers.enumValue(UserStatus),
|
||||
avatar_url: faker.image.avatar(),
|
||||
github_id: faker.string.alphanumeric(10)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机用户档案数据
|
||||
*/
|
||||
static generateUserProfile(seed?: number) {
|
||||
this.setupFaker(seed);
|
||||
return {
|
||||
user_id: faker.string.numeric(10),
|
||||
bio: faker.lorem.paragraph(),
|
||||
resume_content: faker.lorem.paragraphs(3),
|
||||
tags: JSON.stringify(faker.helpers.arrayElements(['developer', 'designer', 'manager'], { min: 1, max: 3 })),
|
||||
social_links: JSON.stringify({
|
||||
github: faker.internet.url(),
|
||||
linkedin: faker.internet.url()
|
||||
}),
|
||||
skin_id: faker.string.alphanumeric(8),
|
||||
current_map: faker.helpers.arrayElement(['plaza', 'forest', 'beach', 'mountain']),
|
||||
pos_x: faker.number.float({ min: 0, max: 1000 }),
|
||||
pos_y: faker.number.float({ min: 0, max: 1000 }),
|
||||
status: faker.number.int({ min: 0, max: 2 })
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机Zulip账号数据
|
||||
*/
|
||||
static generateZulipAccount(seed?: number) {
|
||||
this.setupFaker(seed);
|
||||
return {
|
||||
gameUserId: faker.string.numeric(10),
|
||||
zulipUserId: faker.number.int({ min: 1, max: 999999 }),
|
||||
zulipEmail: faker.internet.email(),
|
||||
zulipFullName: faker.person.fullName(),
|
||||
zulipApiKeyEncrypted: faker.string.alphanumeric(32),
|
||||
status: faker.helpers.arrayElement(['active', 'inactive', 'suspended', 'error'] as const)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机分页参数
|
||||
*/
|
||||
static generatePaginationParams(seed?: number) {
|
||||
this.setupFaker(seed);
|
||||
return {
|
||||
limit: faker.number.int({ min: 1, max: 100 }),
|
||||
offset: faker.number.int({ min: 0, max: 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`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user