Files
whale-town-end/src/business/auth/login.service.zulip_integration.spec.ts
moyin d04ab7f75f CRITICAL ISSUES: Database management service with major problems
WARNING: This commit contains code with significant issues that need immediate attention:

1. Type Safety Issues:
   - Unused import ZulipAccountsService causing compilation warnings
   - Implicit 'any' type in formatZulipAccount method parameter
   - Type inconsistencies in service injections

2. Service Integration Problems:
   - Inconsistent service interface usage
   - Missing proper type definitions for injected services
   - Potential runtime errors due to type mismatches

3. Code Quality Issues:
   - Violation of TypeScript strict mode requirements
   - Inconsistent error handling patterns
   - Missing proper interface implementations

 Files affected:
   - src/business/admin/database_management.service.ts (main issue)
   - Multiple test files and service implementations
   - Configuration and documentation updates

 Next steps required:
   1. Fix TypeScript compilation errors
   2. Implement proper type safety
   3. Resolve service injection inconsistencies
   4. Add comprehensive error handling
   5. Update tests to match new implementations

 Impact: High - affects admin functionality and system stability
 Priority: Urgent - requires immediate review and fixes

Author: moyin
Date: 2026-01-10
2026-01-10 19:27:28 +08:00

443 lines
15 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.
/**
* 登录服务Zulip集成测试
*
* 功能描述:
* - 测试用户注册时的Zulip账号创建/绑定逻辑
* - 测试用户登录时的Zulip集成处理
* - 验证API Key的获取和存储机制
* - 测试各种异常情况的处理
*
* 测试场景:
* - 注册时Zulip中没有用户创建新账号
* - 注册时Zulip中已有用户绑定已有账号
* - 登录时没有Zulip关联尝试创建/绑定
* - 登录时已有Zulip关联刷新API Key
* - 各种错误情况的处理和回滚
*
* @author moyin
* @version 1.0.0
* @since 2026-01-10
* @lastModified 2026-01-10
*/
import { Test, TestingModule } from '@nestjs/testing';
import { Logger } from '@nestjs/common';
import { LoginService } from './login.service';
import { LoginCoreService } from '../../core/login_core/login_core.service';
import { ZulipAccountService } from '../../core/zulip_core/services/zulip_account.service';
import { ZulipAccountsService } from '../../core/db/zulip_accounts/zulip_accounts.service';
import { ApiKeySecurityService } from '../../core/zulip_core/services/api_key_security.service';
import { Users } from '../../core/db/users/users.entity';
import { ZulipAccountResponseDto } from '../../core/db/zulip_accounts/zulip_accounts.dto';
describe('LoginService - Zulip Integration', () => {
let service: LoginService;
let loginCoreService: jest.Mocked<LoginCoreService>;
let zulipAccountService: jest.Mocked<ZulipAccountService>;
let zulipAccountsService: jest.Mocked<ZulipAccountsService>;
let apiKeySecurityService: jest.Mocked<ApiKeySecurityService>;
const mockUser: Users = {
id: BigInt(12345),
username: 'testuser',
nickname: '测试用户',
email: 'test@example.com',
email_verified: false,
phone: null,
password_hash: 'hashedpassword',
github_id: null,
avatar_url: null,
role: 1,
status: 'active',
created_at: new Date(),
updated_at: new Date(),
} as Users;
beforeEach(async () => {
const mockLoginCoreService = {
register: jest.fn(),
login: jest.fn(),
generateTokenPair: jest.fn(),
};
const mockZulipAccountService = {
createZulipAccount: jest.fn(),
initializeAdminClient: jest.fn(),
};
const mockZulipAccountsService = {
findByGameUserId: jest.fn(),
create: jest.fn(),
updateByGameUserId: jest.fn(),
};
const mockApiKeySecurityService = {
storeApiKey: jest.fn(),
getApiKey: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
LoginService,
{
provide: LoginCoreService,
useValue: mockLoginCoreService,
},
{
provide: ZulipAccountService,
useValue: mockZulipAccountService,
},
{
provide: 'ZulipAccountsService',
useValue: mockZulipAccountsService,
},
{
provide: ApiKeySecurityService,
useValue: mockApiKeySecurityService,
},
],
}).compile();
service = module.get<LoginService>(LoginService);
loginCoreService = module.get(LoginCoreService);
zulipAccountService = module.get(ZulipAccountService);
zulipAccountsService = module.get('ZulipAccountsService');
apiKeySecurityService = module.get(ApiKeySecurityService);
// 模拟Logger以避免日志输出
jest.spyOn(Logger.prototype, 'log').mockImplementation();
jest.spyOn(Logger.prototype, 'error').mockImplementation();
jest.spyOn(Logger.prototype, 'warn').mockImplementation();
jest.spyOn(Logger.prototype, 'debug').mockImplementation();
});
describe('用户注册时的Zulip集成', () => {
it('应该在Zulip中不存在用户时创建新账号', async () => {
// 准备测试数据
const registerRequest = {
username: 'testuser',
password: 'password123',
nickname: '测试用户',
email: 'test@example.com',
};
const mockAuthResult = {
user: mockUser,
isNewUser: true,
};
const mockTokenPair = {
access_token: 'access_token',
refresh_token: 'refresh_token',
expires_in: 3600,
token_type: 'Bearer',
};
const mockZulipCreateResult = {
success: true,
userId: 67890,
email: 'test@example.com',
apiKey: 'test_api_key_12345678901234567890',
isExistingUser: false,
};
// 设置模拟返回值
loginCoreService.register.mockResolvedValue(mockAuthResult);
loginCoreService.generateTokenPair.mockResolvedValue(mockTokenPair);
zulipAccountsService.findByGameUserId.mockResolvedValue(null);
zulipAccountService.createZulipAccount.mockResolvedValue(mockZulipCreateResult);
apiKeySecurityService.storeApiKey.mockResolvedValue({ success: true, message: 'Stored' });
zulipAccountsService.create.mockResolvedValue({} as any);
// 模拟私有方法
const checkZulipUserExistsSpy = jest.spyOn(service as any, 'checkZulipUserExists')
.mockResolvedValue({ exists: false });
jest.spyOn(service as any, 'initializeZulipAdminClient')
.mockResolvedValue(true);
// 执行测试
const result = await service.register(registerRequest);
// 验证结果
expect(result.success).toBe(true);
expect(result.data?.is_new_user).toBe(true);
expect(result.data?.message).toContain('Zulip');
// 验证调用
expect(loginCoreService.register).toHaveBeenCalledWith(registerRequest);
expect(zulipAccountsService.findByGameUserId).toHaveBeenCalledWith('12345');
expect(checkZulipUserExistsSpy).toHaveBeenCalledWith('test@example.com');
expect(zulipAccountService.createZulipAccount).toHaveBeenCalledWith({
email: 'test@example.com',
fullName: '测试用户',
password: 'password123',
});
expect(apiKeySecurityService.storeApiKey).toHaveBeenCalledWith('12345', 'test_api_key_12345678901234567890');
expect(zulipAccountsService.create).toHaveBeenCalledWith({
gameUserId: '12345',
zulipUserId: 67890,
zulipEmail: 'test@example.com',
zulipFullName: '测试用户',
zulipApiKeyEncrypted: 'stored_in_redis',
status: 'active',
lastVerifiedAt: expect.any(Date),
});
});
it('应该在Zulip中已存在用户时绑定账号', async () => {
// 准备测试数据
const registerRequest = {
username: 'testuser',
password: 'password123',
nickname: '测试用户',
email: 'test@example.com',
};
const mockAuthResult = {
user: mockUser,
isNewUser: true,
};
const mockTokenPair = {
access_token: 'access_token',
refresh_token: 'refresh_token',
expires_in: 3600,
token_type: 'Bearer',
};
// 设置模拟返回值
loginCoreService.register.mockResolvedValue(mockAuthResult);
loginCoreService.generateTokenPair.mockResolvedValue(mockTokenPair);
zulipAccountsService.findByGameUserId.mockResolvedValue(null);
apiKeySecurityService.storeApiKey.mockResolvedValue({ success: true, message: 'Stored' });
zulipAccountsService.create.mockResolvedValue({} as any);
// 模拟私有方法
jest.spyOn(service as any, 'checkZulipUserExists')
.mockResolvedValue({ exists: true, userId: 67890 });
const refreshZulipApiKeySpy = jest.spyOn(service as any, 'refreshZulipApiKey')
.mockResolvedValue({ success: true, apiKey: 'existing_api_key_12345678901234567890' });
jest.spyOn(service as any, 'initializeZulipAdminClient')
.mockResolvedValue(true);
// 执行测试
const result = await service.register(registerRequest);
// 验证结果
expect(result.success).toBe(true);
expect(result.data?.message).toContain('绑定');
// 验证调用
expect(refreshZulipApiKeySpy).toHaveBeenCalledWith('test@example.com', 'password123');
expect(apiKeySecurityService.storeApiKey).toHaveBeenCalledWith('12345', 'existing_api_key_12345678901234567890');
expect(zulipAccountsService.create).toHaveBeenCalledWith({
gameUserId: '12345',
zulipUserId: 67890,
zulipEmail: 'test@example.com',
zulipFullName: '测试用户',
zulipApiKeyEncrypted: 'stored_in_redis',
status: 'active',
lastVerifiedAt: expect.any(Date),
});
});
});
describe('用户登录时的Zulip集成', () => {
it('应该在用户没有Zulip关联时尝试创建/绑定', async () => {
// 准备测试数据
const loginRequest = {
identifier: 'testuser',
password: 'password123',
};
const mockAuthResult = {
user: mockUser,
isNewUser: false,
};
const mockTokenPair = {
access_token: 'access_token',
refresh_token: 'refresh_token',
expires_in: 3600,
token_type: 'Bearer',
};
const mockZulipCreateResult = {
success: true,
userId: 67890,
email: 'test@example.com',
apiKey: 'new_api_key_12345678901234567890',
isExistingUser: false,
};
// 设置模拟返回值
loginCoreService.login.mockResolvedValue(mockAuthResult);
loginCoreService.generateTokenPair.mockResolvedValue(mockTokenPair);
zulipAccountsService.findByGameUserId.mockResolvedValue(null);
zulipAccountService.createZulipAccount.mockResolvedValue(mockZulipCreateResult);
apiKeySecurityService.storeApiKey.mockResolvedValue({ success: true, message: 'Stored' });
zulipAccountsService.create.mockResolvedValue({} as any);
// 模拟私有方法
jest.spyOn(service as any, 'checkZulipUserExists')
.mockResolvedValue({ exists: false });
jest.spyOn(service as any, 'initializeZulipAdminClient')
.mockResolvedValue(true);
// 执行测试
const result = await service.login(loginRequest);
// 验证结果
expect(result.success).toBe(true);
expect(result.data?.is_new_user).toBe(false);
// 验证调用
expect(loginCoreService.login).toHaveBeenCalledWith(loginRequest);
expect(zulipAccountsService.findByGameUserId).toHaveBeenCalledWith('12345');
expect(zulipAccountService.createZulipAccount).toHaveBeenCalledWith({
email: 'test@example.com',
fullName: '测试用户',
password: 'password123',
});
});
it('应该在用户已有Zulip关联时刷新API Key', async () => {
// 准备测试数据
const loginRequest = {
identifier: 'testuser',
password: 'password123',
};
const mockAuthResult = {
user: mockUser,
isNewUser: false,
};
const mockTokenPair = {
access_token: 'access_token',
refresh_token: 'refresh_token',
expires_in: 3600,
token_type: 'Bearer',
};
const mockExistingAccount: ZulipAccountResponseDto = {
id: '1',
gameUserId: '12345',
zulipUserId: 67890,
zulipEmail: 'test@example.com',
zulipFullName: '测试用户',
status: 'active' as const,
lastVerifiedAt: new Date().toISOString(),
retryCount: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
// 设置模拟返回值
loginCoreService.login.mockResolvedValue(mockAuthResult);
loginCoreService.generateTokenPair.mockResolvedValue(mockTokenPair);
zulipAccountsService.findByGameUserId.mockResolvedValue(mockExistingAccount);
apiKeySecurityService.storeApiKey.mockResolvedValue({ success: true, message: 'Stored' });
zulipAccountsService.updateByGameUserId.mockResolvedValue({} as any);
// 模拟私有方法
const refreshZulipApiKeySpy = jest.spyOn(service as any, 'refreshZulipApiKey')
.mockResolvedValue({ success: true, apiKey: 'refreshed_api_key_12345678901234567890' });
// 执行测试
const result = await service.login(loginRequest);
// 验证结果
expect(result.success).toBe(true);
// 验证调用
expect(zulipAccountsService.findByGameUserId).toHaveBeenCalledWith('12345');
expect(refreshZulipApiKeySpy).toHaveBeenCalledWith('test@example.com', 'password123');
expect(apiKeySecurityService.storeApiKey).toHaveBeenCalledWith('12345', 'refreshed_api_key_12345678901234567890');
expect(zulipAccountsService.updateByGameUserId).toHaveBeenCalledWith('12345', {
lastVerifiedAt: expect.any(Date),
status: 'active',
errorMessage: null,
});
});
});
describe('错误处理', () => {
it('应该在Zulip创建失败时回滚用户注册', async () => {
// 准备测试数据
const registerRequest = {
username: 'testuser',
password: 'password123',
nickname: '测试用户',
email: 'test@example.com',
};
const mockAuthResult = {
user: mockUser,
isNewUser: true,
};
// 设置模拟返回值
loginCoreService.register.mockResolvedValue(mockAuthResult);
loginCoreService.deleteUser = jest.fn().mockResolvedValue(true);
zulipAccountsService.findByGameUserId.mockResolvedValue(null);
// 模拟Zulip创建失败
jest.spyOn(service as any, 'checkZulipUserExists')
.mockResolvedValue({ exists: false });
jest.spyOn(service as any, 'initializeZulipAdminClient')
.mockResolvedValue(true);
zulipAccountService.createZulipAccount.mockResolvedValue({
success: false,
error: 'Zulip服务器错误',
});
// 执行测试
const result = await service.register(registerRequest);
// 验证结果
expect(result.success).toBe(false);
expect(result.message).toContain('Zulip账号创建失败');
// 验证回滚调用
expect(loginCoreService.deleteUser).toHaveBeenCalledWith(mockUser.id);
});
it('应该在登录时Zulip集成失败但不影响登录', async () => {
// 准备测试数据
const loginRequest = {
identifier: 'testuser',
password: 'password123',
};
const mockAuthResult = {
user: mockUser,
isNewUser: false,
};
const mockTokenPair = {
access_token: 'access_token',
refresh_token: 'refresh_token',
expires_in: 3600,
token_type: 'Bearer',
};
// 设置模拟返回值
loginCoreService.login.mockResolvedValue(mockAuthResult);
loginCoreService.generateTokenPair.mockResolvedValue(mockTokenPair);
zulipAccountsService.findByGameUserId.mockResolvedValue(null);
// 模拟Zulip集成失败
jest.spyOn(service as any, 'initializeZulipAdminClient')
.mockRejectedValue(new Error('Zulip服务器不可用'));
// 执行测试
const result = await service.login(loginRequest);
// 验证结果 - 登录应该成功即使Zulip集成失败
expect(result.success).toBe(true);
expect(result.data?.access_token).toBe('access_token');
});
});
});