forked from datawhale/whale-town-end
refactor:重构Zulip模块按业务功能模块化架构
- 将技术实现服务从business层迁移到core层 - 创建src/core/zulip/核心服务模块,包含API客户端、连接池等技术服务 - 保留src/business/zulip/业务逻辑,专注游戏相关的业务规则 - 通过依赖注入实现业务层与核心层的解耦 - 更新模块导入关系,确保架构分层清晰 重构后的架构符合单一职责原则,提高了代码的可维护性和可测试性
This commit is contained in:
598
src/core/zulip/services/config_manager.service.spec.ts
Normal file
598
src/core/zulip/services/config_manager.service.spec.ts
Normal file
@@ -0,0 +1,598 @@
|
||||
/**
|
||||
* 配置管理服务测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试ConfigManagerService的核心功能
|
||||
* - 包含属性测试验证配置验证正确性
|
||||
*
|
||||
* @author angjustinl
|
||||
* @version 1.0.0
|
||||
* @since 2025-12-25
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import * as fc from 'fast-check';
|
||||
import { ConfigManagerService, MapConfig, ZulipConfig } from './config_manager.service';
|
||||
import { AppLoggerService } from '../../utils/logger/logger.service';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// Mock fs module
|
||||
jest.mock('fs');
|
||||
|
||||
describe('ConfigManagerService', () => {
|
||||
let service: ConfigManagerService;
|
||||
let mockLogger: jest.Mocked<AppLoggerService>;
|
||||
const mockFs = fs as jest.Mocked<typeof fs>;
|
||||
|
||||
// 默认有效配置
|
||||
const validMapConfig = {
|
||||
maps: [
|
||||
{
|
||||
mapId: 'novice_village',
|
||||
mapName: '新手村',
|
||||
zulipStream: 'Novice Village',
|
||||
interactionObjects: [
|
||||
{
|
||||
objectId: 'notice_board',
|
||||
objectName: '公告板',
|
||||
zulipTopic: 'Notice Board',
|
||||
position: { x: 100, y: 150 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
mapId: 'tavern',
|
||||
mapName: '酒馆',
|
||||
zulipStream: 'Tavern',
|
||||
interactionObjects: [
|
||||
{
|
||||
objectId: 'bar_counter',
|
||||
objectName: '吧台',
|
||||
zulipTopic: 'Bar Counter',
|
||||
position: { x: 150, y: 100 }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockLogger = {
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
} as any;
|
||||
|
||||
// 默认mock fs行为
|
||||
mockFs.existsSync.mockReturnValue(true);
|
||||
mockFs.readFileSync.mockReturnValue(JSON.stringify(validMapConfig));
|
||||
mockFs.writeFileSync.mockImplementation(() => {});
|
||||
mockFs.mkdirSync.mockImplementation(() => undefined);
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ConfigManagerService,
|
||||
{
|
||||
provide: AppLoggerService,
|
||||
useValue: mockLogger,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ConfigManagerService>(ConfigManagerService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('loadMapConfig - 加载地图配置', () => {
|
||||
it('应该成功加载有效的地图配置', async () => {
|
||||
await service.loadMapConfig();
|
||||
|
||||
const mapIds = service.getAllMapIds();
|
||||
expect(mapIds).toContain('novice_village');
|
||||
expect(mapIds).toContain('tavern');
|
||||
});
|
||||
|
||||
it('应该在配置文件不存在时创建默认配置', async () => {
|
||||
mockFs.existsSync.mockReturnValue(false);
|
||||
|
||||
await service.loadMapConfig();
|
||||
|
||||
expect(mockFs.writeFileSync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('应该在配置格式无效时抛出错误', async () => {
|
||||
mockFs.readFileSync.mockReturnValue(JSON.stringify({ invalid: 'config' }));
|
||||
|
||||
await expect(service.loadMapConfig()).rejects.toThrow('配置格式无效');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStreamByMap - 根据地图获取Stream', () => {
|
||||
it('应该返回正确的Stream名称', () => {
|
||||
const stream = service.getStreamByMap('novice_village');
|
||||
expect(stream).toBe('Novice Village');
|
||||
});
|
||||
|
||||
it('应该在地图不存在时返回null', () => {
|
||||
const stream = service.getStreamByMap('nonexistent');
|
||||
expect(stream).toBeNull();
|
||||
});
|
||||
|
||||
it('应该在mapId为空时返回null', () => {
|
||||
const stream = service.getStreamByMap('');
|
||||
expect(stream).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMapConfig - 获取地图配置', () => {
|
||||
it('应该返回完整的地图配置', () => {
|
||||
const config = service.getMapConfig('novice_village');
|
||||
expect(config).toBeDefined();
|
||||
expect(config?.mapId).toBe('novice_village');
|
||||
expect(config?.mapName).toBe('新手村');
|
||||
expect(config?.zulipStream).toBe('Novice Village');
|
||||
});
|
||||
|
||||
it('应该在地图不存在时返回null', () => {
|
||||
const config = service.getMapConfig('nonexistent');
|
||||
expect(config).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTopicByObject - 根据交互对象获取Topic', () => {
|
||||
it('应该返回正确的Topic名称', () => {
|
||||
const topic = service.getTopicByObject('novice_village', 'notice_board');
|
||||
expect(topic).toBe('Notice Board');
|
||||
});
|
||||
|
||||
it('应该在对象不存在时返回null', () => {
|
||||
const topic = service.getTopicByObject('novice_village', 'nonexistent');
|
||||
expect(topic).toBeNull();
|
||||
});
|
||||
|
||||
it('应该在地图不存在时返回null', () => {
|
||||
const topic = service.getTopicByObject('nonexistent', 'notice_board');
|
||||
expect(topic).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findNearbyObject - 查找附近的交互对象', () => {
|
||||
it('应该找到半径内的交互对象', () => {
|
||||
const obj = service.findNearbyObject('novice_village', 110, 160, 50);
|
||||
expect(obj).toBeDefined();
|
||||
expect(obj?.objectId).toBe('notice_board');
|
||||
});
|
||||
|
||||
it('应该在没有附近对象时返回null', () => {
|
||||
const obj = service.findNearbyObject('novice_village', 500, 500, 50);
|
||||
expect(obj).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMapIdByStream - 根据Stream获取地图ID', () => {
|
||||
it('应该返回正确的地图ID', () => {
|
||||
const mapId = service.getMapIdByStream('Novice Village');
|
||||
expect(mapId).toBe('novice_village');
|
||||
});
|
||||
|
||||
it('应该支持大小写不敏感查询', () => {
|
||||
const mapId = service.getMapIdByStream('novice village');
|
||||
expect(mapId).toBe('novice_village');
|
||||
});
|
||||
|
||||
it('应该在Stream不存在时返回null', () => {
|
||||
const mapId = service.getMapIdByStream('nonexistent');
|
||||
expect(mapId).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateConfig - 验证配置', () => {
|
||||
it('应该对有效配置返回valid=true(除了API Key警告)', async () => {
|
||||
const result = await service.validateConfig();
|
||||
// 由于测试环境没有设置API Key,会有一个错误
|
||||
// 但地图配置应该是有效的
|
||||
expect(result.errors.some(e => e.includes('地图配置无效'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateMapConfigDetailed - 详细验证地图配置', () => {
|
||||
it('应该对有效配置返回valid=true', () => {
|
||||
const config = {
|
||||
mapId: 'test_map',
|
||||
mapName: '测试地图',
|
||||
zulipStream: 'Test Stream',
|
||||
interactionObjects: [] as any[]
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('应该检测缺少mapId的错误', () => {
|
||||
const config = {
|
||||
mapName: '测试地图',
|
||||
zulipStream: 'Test Stream',
|
||||
interactionObjects: [] as any[]
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors).toContain('缺少mapId字段');
|
||||
});
|
||||
|
||||
it('应该检测缺少mapName的错误', () => {
|
||||
const config = {
|
||||
mapId: 'test_map',
|
||||
zulipStream: 'Test Stream',
|
||||
interactionObjects: [] as any[]
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors).toContain('缺少mapName字段');
|
||||
});
|
||||
|
||||
it('应该检测缺少zulipStream的错误', () => {
|
||||
const config = {
|
||||
mapId: 'test_map',
|
||||
mapName: '测试地图',
|
||||
interactionObjects: [] as any[]
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors).toContain('缺少zulipStream字段');
|
||||
});
|
||||
|
||||
it('应该检测交互对象中缺少字段的错误', () => {
|
||||
const config = {
|
||||
mapId: 'test_map',
|
||||
mapName: '测试地图',
|
||||
zulipStream: 'Test Stream',
|
||||
interactionObjects: [
|
||||
{
|
||||
objectId: 'test_obj',
|
||||
// 缺少objectName, zulipTopic, position
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConfigStats - 获取配置统计', () => {
|
||||
it('应该返回正确的统计信息', () => {
|
||||
const stats = service.getConfigStats();
|
||||
expect(stats.mapCount).toBe(2);
|
||||
expect(stats.totalObjects).toBe(2);
|
||||
expect(stats.isValid).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* 属性测试: 配置验证
|
||||
*
|
||||
* **Feature: zulip-integration, Property 12: 配置验证**
|
||||
* **Validates: Requirements 10.5**
|
||||
*
|
||||
* 对于任何系统配置,系统应该在启动时验证配置的有效性,
|
||||
* 并在发现无效配置时报告详细的错误信息
|
||||
*/
|
||||
describe('Property 12: 配置验证', () => {
|
||||
/**
|
||||
* 属性: 对于任何有效的地图配置,验证应该返回valid=true
|
||||
* 验证需求 10.5: 验证配置时系统应在启动时检查配置的有效性并报告错误
|
||||
*/
|
||||
it('对于任何有效的地图配置,验证应该返回valid=true', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成有效的mapId
|
||||
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
|
||||
// 生成有效的mapName
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
// 生成有效的zulipStream
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
// 生成有效的交互对象数组
|
||||
fc.array(
|
||||
fc.record({
|
||||
objectId: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
|
||||
objectName: fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
zulipTopic: fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
position: fc.record({
|
||||
x: fc.integer({ min: 0, max: 10000 }),
|
||||
y: fc.integer({ min: 0, max: 10000 }),
|
||||
}),
|
||||
}),
|
||||
{ minLength: 0, maxLength: 10 }
|
||||
),
|
||||
async (mapId, mapName, zulipStream, interactionObjects) => {
|
||||
const config = {
|
||||
mapId: mapId.trim(),
|
||||
mapName: mapName.trim(),
|
||||
zulipStream: zulipStream.trim(),
|
||||
interactionObjects: interactionObjects.map(obj => ({
|
||||
objectId: obj.objectId.trim(),
|
||||
objectName: obj.objectName.trim(),
|
||||
zulipTopic: obj.zulipTopic.trim(),
|
||||
position: obj.position,
|
||||
})),
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
|
||||
// 有效配置应该通过验证
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.errors).toHaveLength(0);
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
/**
|
||||
* 属性: 对于任何缺少必填字段的配置,验证应该返回valid=false并包含错误信息
|
||||
* 验证需求 10.5: 验证配置时系统应在启动时检查配置的有效性并报告错误
|
||||
*/
|
||||
it('对于任何缺少mapId的配置,验证应该返回valid=false', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成有效的mapName
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
// 生成有效的zulipStream
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
async (mapName, zulipStream) => {
|
||||
const config = {
|
||||
// 缺少mapId
|
||||
mapName: mapName.trim(),
|
||||
zulipStream: zulipStream.trim(),
|
||||
interactionObjects: [] as any[],
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
|
||||
// 缺少mapId应该验证失败
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e => e.includes('mapId'))).toBe(true);
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
/**
|
||||
* 属性: 对于任何缺少mapName的配置,验证应该返回valid=false
|
||||
*/
|
||||
it('对于任何缺少mapName的配置,验证应该返回valid=false', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成有效的mapId
|
||||
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
|
||||
// 生成有效的zulipStream
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
async (mapId, zulipStream) => {
|
||||
const config = {
|
||||
mapId: mapId.trim(),
|
||||
// 缺少mapName
|
||||
zulipStream: zulipStream.trim(),
|
||||
interactionObjects: [] as any[],
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
|
||||
// 缺少mapName应该验证失败
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e => e.includes('mapName'))).toBe(true);
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
/**
|
||||
* 属性: 对于任何缺少zulipStream的配置,验证应该返回valid=false
|
||||
*/
|
||||
it('对于任何缺少zulipStream的配置,验证应该返回valid=false', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成有效的mapId
|
||||
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
|
||||
// 生成有效的mapName
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
async (mapId, mapName) => {
|
||||
const config = {
|
||||
mapId: mapId.trim(),
|
||||
mapName: mapName.trim(),
|
||||
// 缺少zulipStream
|
||||
interactionObjects: [] as any[],
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
|
||||
// 缺少zulipStream应该验证失败
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e => e.includes('zulipStream'))).toBe(true);
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
/**
|
||||
* 属性: 对于任何交互对象缺少必填字段的配置,验证应该返回valid=false
|
||||
*/
|
||||
it('对于任何交互对象缺少objectId的配置,验证应该返回valid=false', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成有效的地图配置
|
||||
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
// 生成有效的交互对象(但缺少objectId)
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
fc.integer({ min: 0, max: 10000 }),
|
||||
fc.integer({ min: 0, max: 10000 }),
|
||||
async (mapId, mapName, zulipStream, objectName, zulipTopic, x, y) => {
|
||||
const config = {
|
||||
mapId: mapId.trim(),
|
||||
mapName: mapName.trim(),
|
||||
zulipStream: zulipStream.trim(),
|
||||
interactionObjects: [
|
||||
{
|
||||
// 缺少objectId
|
||||
objectName: objectName.trim(),
|
||||
zulipTopic: zulipTopic.trim(),
|
||||
position: { x, y },
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
|
||||
// 缺少objectId应该验证失败
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e => e.includes('objectId'))).toBe(true);
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
/**
|
||||
* 属性: 对于任何交互对象position无效的配置,验证应该返回valid=false
|
||||
*/
|
||||
it('对于任何交互对象position无效的配置,验证应该返回valid=false', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成有效的地图配置
|
||||
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
// 生成有效的交互对象字段
|
||||
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
async (mapId, mapName, zulipStream, objectId, objectName, zulipTopic) => {
|
||||
const config = {
|
||||
mapId: mapId.trim(),
|
||||
mapName: mapName.trim(),
|
||||
zulipStream: zulipStream.trim(),
|
||||
interactionObjects: [
|
||||
{
|
||||
objectId: objectId.trim(),
|
||||
objectName: objectName.trim(),
|
||||
zulipTopic: zulipTopic.trim(),
|
||||
// 缺少position
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
|
||||
// 缺少position应该验证失败
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e => e.includes('position'))).toBe(true);
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
/**
|
||||
* 属性: 验证结果的错误数量应该与实际错误数量一致
|
||||
*/
|
||||
it('验证结果的错误数量应该与实际错误数量一致', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 随机决定是否包含各个字段
|
||||
fc.boolean(),
|
||||
fc.boolean(),
|
||||
fc.boolean(),
|
||||
// 生成字段值
|
||||
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
async (includeMapId, includeMapName, includeZulipStream, mapId, mapName, zulipStream) => {
|
||||
const config: any = {
|
||||
interactionObjects: [] as any[],
|
||||
};
|
||||
|
||||
let expectedErrors = 0;
|
||||
|
||||
if (includeMapId) {
|
||||
config.mapId = mapId.trim();
|
||||
} else {
|
||||
expectedErrors++;
|
||||
}
|
||||
|
||||
if (includeMapName) {
|
||||
config.mapName = mapName.trim();
|
||||
} else {
|
||||
expectedErrors++;
|
||||
}
|
||||
|
||||
if (includeZulipStream) {
|
||||
config.zulipStream = zulipStream.trim();
|
||||
} else {
|
||||
expectedErrors++;
|
||||
}
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
|
||||
// 错误数量应该与预期一致
|
||||
expect(result.errors.length).toBe(expectedErrors);
|
||||
expect(result.valid).toBe(expectedErrors === 0);
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
/**
|
||||
* 属性: 对于任何空字符串字段,验证应该返回valid=false
|
||||
*/
|
||||
it('对于任何空字符串字段,验证应该返回valid=false', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 随机选择哪个字段为空
|
||||
fc.constantFrom('mapId', 'mapName', 'zulipStream'),
|
||||
// 生成有效的字段值
|
||||
fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
async (emptyField, mapId, mapName, zulipStream) => {
|
||||
const config: any = {
|
||||
mapId: emptyField === 'mapId' ? '' : mapId.trim(),
|
||||
mapName: emptyField === 'mapName' ? '' : mapName.trim(),
|
||||
zulipStream: emptyField === 'zulipStream' ? '' : zulipStream.trim(),
|
||||
interactionObjects: [] as any[],
|
||||
};
|
||||
|
||||
const result = service.validateMapConfigDetailed(config);
|
||||
|
||||
// 空字符串字段应该验证失败
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e => e.includes(emptyField))).toBe(true);
|
||||
}
|
||||
),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
}, 60000);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user