Files
whale-town-end/src/core/redis/real_redis.integration.spec.ts
moyin bb796a2469 refactor:项目架构重构和命名规范化
- 统一文件命名为snake_case格式(kebab-case  snake_case)
- 重构zulip模块为zulip_core,明确Core层职责
- 重构user-mgmt模块为user_mgmt,统一命名规范
- 调整模块依赖关系,优化架构分层
- 删除过时的文件和目录结构
- 更新相关文档和配置文件

本次重构涉及大量文件重命名和模块重组,
旨在建立更清晰的项目架构和统一的命名规范。
2026-01-08 00:14:14 +08:00

553 lines
16 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.
/**
* RealRedisService集成测试
*
* 功能描述:
* - 使用真实Redis连接进行集成测试
* - 测试Redis服务器连接和断开
* - 验证数据持久性和一致性
* - 测试Redis服务的完整工作流程
*
* 职责分离:
* - 集成测试测试与真实Redis服务器的交互
* - 连接测试验证Redis连接管理
* - 数据一致性:测试数据的持久化和读取
* - 性能测试验证Redis操作的性能表现
*
* 最近修改:
* - 2025-01-07: 功能新增 - 创建RealRedisService完整集成测试验证真实Redis交互
*
* @author moyin
* @version 1.0.0
* @since 2025-01-07
* @lastModified 2025-01-07
*/
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { RealRedisService } from './real_redis.service';
import Redis from 'ioredis';
describe('RealRedisService Integration', () => {
let service: RealRedisService;
let module: TestingModule;
let configService: ConfigService;
// 测试配置 - 使用测试Redis实例
const testRedisConfig = {
REDIS_HOST: process.env.TEST_REDIS_HOST || 'localhost',
REDIS_PORT: parseInt(process.env.TEST_REDIS_PORT || '6379'),
REDIS_PASSWORD: process.env.TEST_REDIS_PASSWORD,
REDIS_DB: parseInt(process.env.TEST_REDIS_DB || '15'), // 使用DB 15进行测试
};
beforeAll(async () => {
// 检查是否有可用的Redis服务器
const testRedis = new Redis({
host: testRedisConfig.REDIS_HOST,
port: testRedisConfig.REDIS_PORT,
password: testRedisConfig.REDIS_PASSWORD,
db: testRedisConfig.REDIS_DB,
lazyConnect: true,
maxRetriesPerRequest: 1,
});
try {
await testRedis.ping();
// 确保连接被正确断开
testRedis.disconnect(false);
} catch (error) {
console.warn('Redis服务器不可用跳过集成测试:', (error as Error).message);
// 确保连接被正确断开
testRedis.disconnect(false);
return;
}
// 创建测试模块
module = await Test.createTestingModule({
providers: [
RealRedisService,
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string, defaultValue?: any) => {
return testRedisConfig[key] || defaultValue;
}),
},
},
],
}).compile();
service = module.get<RealRedisService>(RealRedisService);
configService = module.get<ConfigService>(ConfigService);
// 清空测试数据库
try {
await service.flushall();
} catch (error) {
// 忽略清空数据时的错误
}
});
afterAll(async () => {
if (service) {
try {
// 清空测试数据
await service.flushall();
} catch (error) {
// 忽略清空数据时的错误
}
try {
// 断开连接
service.onModuleDestroy();
} catch (error) {
// 忽略断开连接时的错误
}
}
if (module) {
await module.close();
}
});
beforeEach(async () => {
// 每个测试前清空数据
if (service) {
try {
await service.flushall();
} catch (error) {
// 忽略清空数据时的错误
}
}
});
// 检查Redis是否可用的辅助函数
const skipIfRedisUnavailable = () => {
if (!service) {
return true; // 返回true表示应该跳过测试
}
return false;
};
describe('Redis连接管理', () => {
it('should connect to Redis server successfully', () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
expect(service).toBeDefined();
});
it('should use correct Redis configuration', () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
expect(configService.get).toHaveBeenCalledWith('REDIS_HOST', 'localhost');
expect(configService.get).toHaveBeenCalledWith('REDIS_PORT', 6379);
expect(configService.get).toHaveBeenCalledWith('REDIS_DB', 0);
});
});
describe('基础键值操作', () => {
it('should set and get string values', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.set('test:string', 'Hello Redis');
const result = await service.get('test:string');
expect(result).toBe('Hello Redis');
});
it('should handle non-existent keys', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
const result = await service.get('test:nonexistent');
expect(result).toBeNull();
});
it('should delete existing keys', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.set('test:delete', 'to be deleted');
const deleted = await service.del('test:delete');
const result = await service.get('test:delete');
expect(deleted).toBe(true);
expect(result).toBeNull();
});
it('should return false when deleting non-existent keys', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
const deleted = await service.del('test:nonexistent');
expect(deleted).toBe(false);
});
it('should check key existence', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.set('test:exists', 'exists');
const exists = await service.exists('test:exists');
const notExists = await service.exists('test:notexists');
expect(exists).toBe(true);
expect(notExists).toBe(false);
});
});
describe('过期时间管理', () => {
it('should set keys with TTL', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.set('test:ttl', 'expires soon', 2);
const ttl = await service.ttl('test:ttl');
expect(ttl).toBeGreaterThan(0);
expect(ttl).toBeLessThanOrEqual(2);
});
it('should expire keys after TTL', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.set('test:expire', 'will expire', 1);
// 等待过期
await new Promise(resolve => setTimeout(resolve, 1100));
const result = await service.get('test:expire');
expect(result).toBeNull();
}, 2000);
it('should set expiration on existing keys', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.set('test:expire_later', 'set expiration later');
await service.expire('test:expire_later', 2);
const ttl = await service.ttl('test:expire_later');
expect(ttl).toBeGreaterThan(0);
expect(ttl).toBeLessThanOrEqual(2);
});
it('should return -1 for keys without expiration', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.set('test:no_expire', 'never expires');
const ttl = await service.ttl('test:no_expire');
expect(ttl).toBe(-1);
});
it('should return -2 for non-existent keys', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
const ttl = await service.ttl('test:nonexistent');
expect(ttl).toBe(-2);
});
it('should use setex for atomic set with expiration', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.setex('test:setex', 2, 'atomic set with expiration');
const value = await service.get('test:setex');
const ttl = await service.ttl('test:setex');
expect(value).toBe('atomic set with expiration');
expect(ttl).toBeGreaterThan(0);
expect(ttl).toBeLessThanOrEqual(2);
});
});
describe('数值操作', () => {
it('should increment numeric values', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
const result1 = await service.incr('test:counter');
const result2 = await service.incr('test:counter');
const result3 = await service.incr('test:counter');
expect(result1).toBe(1);
expect(result2).toBe(2);
expect(result3).toBe(3);
});
it('should increment existing numeric values', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.set('test:existing_counter', '10');
const result = await service.incr('test:existing_counter');
expect(result).toBe(11);
});
});
describe('集合操作', () => {
it('should add and retrieve set members', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.sadd('test:set', 'member1');
await service.sadd('test:set', 'member2');
await service.sadd('test:set', 'member3');
const members = await service.smembers('test:set');
expect(members).toHaveLength(3);
expect(members).toContain('member1');
expect(members).toContain('member2');
expect(members).toContain('member3');
});
it('should remove set members', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.sadd('test:set_remove', 'member1');
await service.sadd('test:set_remove', 'member2');
await service.sadd('test:set_remove', 'member3');
await service.srem('test:set_remove', 'member2');
const members = await service.smembers('test:set_remove');
expect(members).toHaveLength(2);
expect(members).toContain('member1');
expect(members).toContain('member3');
expect(members).not.toContain('member2');
});
it('should return empty array for non-existent sets', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
const members = await service.smembers('test:nonexistent_set');
expect(members).toEqual([]);
});
it('should handle duplicate set members', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.sadd('test:duplicate_set', 'member1');
await service.sadd('test:duplicate_set', 'member1'); // 重复添加
await service.sadd('test:duplicate_set', 'member2');
const members = await service.smembers('test:duplicate_set');
expect(members).toHaveLength(2);
expect(members).toContain('member1');
expect(members).toContain('member2');
});
});
describe('数据持久性和一致性', () => {
it('should persist data across operations', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
// 设置多种类型的数据
await service.set('test:persist:string', 'persistent string');
await service.set('test:persist:number', '42');
await service.sadd('test:persist:set', 'set_member');
await service.incr('test:persist:counter');
// 验证数据持久性
const stringValue = await service.get('test:persist:string');
const numberValue = await service.get('test:persist:number');
const setMembers = await service.smembers('test:persist:set');
const counterValue = await service.get('test:persist:counter');
expect(stringValue).toBe('persistent string');
expect(numberValue).toBe('42');
expect(setMembers).toContain('set_member');
expect(counterValue).toBe('1');
});
it('should maintain data consistency during concurrent operations', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
// 并发执行多个操作
const promises = [];
for (let i = 0; i < 10; i++) {
promises.push(service.incr('test:concurrent:counter'));
promises.push(service.sadd('test:concurrent:set', `member${i}`));
}
await Promise.all(promises);
// 验证结果一致性
const counterValue = await service.get('test:concurrent:counter');
const setMembers = await service.smembers('test:concurrent:set');
expect(parseInt(counterValue)).toBe(10);
expect(setMembers).toHaveLength(10);
});
});
describe('清空操作', () => {
it('should clear all data with flushall', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
// 设置一些测试数据
await service.set('test:flush1', 'value1');
await service.set('test:flush2', 'value2');
await service.sadd('test:flush_set', 'member');
// 清空所有数据
await service.flushall();
// 验证数据已清空
const value1 = await service.get('test:flush1');
const value2 = await service.get('test:flush2');
const setMembers = await service.smembers('test:flush_set');
expect(value1).toBeNull();
expect(value2).toBeNull();
expect(setMembers).toEqual([]);
});
});
describe('错误处理和边界情况', () => {
it('should handle empty string keys and values', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
await service.set('', 'empty key');
await service.set('empty_value', '');
const emptyKeyValue = await service.get('');
const emptyValue = await service.get('empty_value');
expect(emptyKeyValue).toBe('empty key');
expect(emptyValue).toBe('');
});
it('should handle very long keys and values', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
const longKey = 'test:' + 'a'.repeat(1000);
const longValue = 'b'.repeat(10000);
await service.set(longKey, longValue);
const result = await service.get(longKey);
expect(result).toBe(longValue);
});
it('should handle special characters in keys and values', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
const specialKey = 'test:特殊字符:🚀:key';
const specialValue = 'Special value with 特殊字符 and 🎉 emojis';
await service.set(specialKey, specialValue);
const result = await service.get(specialKey);
expect(result).toBe(specialValue);
});
});
describe('性能测试', () => {
it('should handle multiple operations efficiently', async () => {
if (skipIfRedisUnavailable()) {
console.log('跳过测试Redis服务器不可用');
return;
}
const startTime = Date.now();
const operations = 100;
// 执行大量操作
const promises = [];
for (let i = 0; i < operations; i++) {
promises.push(service.set(`test:perf:${i}`, `value${i}`));
}
await Promise.all(promises);
// 读取所有数据
const readPromises = [];
for (let i = 0; i < operations; i++) {
readPromises.push(service.get(`test:perf:${i}`));
}
const results = await Promise.all(readPromises);
const endTime = Date.now();
const duration = endTime - startTime;
// 验证结果正确性
expect(results).toHaveLength(operations);
results.forEach((result, index) => {
expect(result).toBe(`value${index}`);
});
// 性能检查(应该在合理时间内完成)
expect(duration).toBeLessThan(5000); // 5秒内完成
}, 10000);
});
});