Files
knowai/test/unit/auth/auth-service.test.ts
tobegold574 6a81b7bb13
Some checks reported errors
continuous-integration/drone/push Build was killed
feat(image): 新建 knowai-core:1.0.0 镜像并完成推送
- 搭建 api、auth、utils 等逻辑模块
- 通过 tsc、eslint、vitest 测试验证

BREAKING CHANGE: 新镜像分支
2025-11-10 20:20:25 +08:00

380 lines
12 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.

/**
* 认证服务集成测试
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { createAuthService } from '@/auth';
import { apiClient } from '@/api';
import { createMockStorage } from '@/test/mocks';
import type { StorageAdapter } from '@/auth/storage-adapter';
import type { LoginRequest, LoginResponse, RegisterRequest, RegisterResponse } from '@/types/user/profile';
import { ApiError } from '@/api/errors';
import { AuthError } from '@/auth/errors';
// 模拟API客户端
vi.mock('@/api', () => ({
apiClient: {
addRequestInterceptor: vi.fn(),
addResponseInterceptor: vi.fn(),
removeRequestInterceptor: vi.fn(),
removeResponseInterceptor: vi.fn(),
setDefaults: vi.fn(),
setBaseURL: vi.fn(),
createInstance: vi.fn(),
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
patch: vi.fn(),
request: vi.fn()
}
}));
// 模拟错误处理
vi.mock('@/api/errors', () => ({
isApiError: vi.fn((error: unknown): error is ApiError =>
error && typeof error === 'object' && 'isApiError' in error && (error as any).isApiError
),
ApiError: class extends Error {
public readonly code: number;
public readonly details: unknown;
constructor(code: number, message: string, details?: unknown) {
super(message);
this.name = 'ApiError';
this.code = code;
this.details = details;
}
}
}));
describe('认证服务', () => {
let authService: ReturnType<typeof createAuthService>;
let mockStorage: StorageAdapter;
beforeEach(() => {
mockStorage = createMockStorage();
authService = createAuthService(apiClient);
vi.clearAllMocks();
});
describe('login', () => {
it('应该成功登录并返回用户信息', async () => {
// 准备测试数据
const loginData: LoginRequest = {
username: 'testuser',
password: 'password123'
};
const loginResponse: LoginResponse = {
user: {
id: '1',
username: 'testuser',
email: 'test@example.com',
avatar: '',
nickname: 'Test User',
bio: '',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
},
sessionId: 'test-session-id'
};
// 模拟API响应
(apiClient.post as any).mockResolvedValue(loginResponse);
// 模拟getUserInfo调用
(apiClient.get as any).mockResolvedValue({ user: loginResponse.user });
// 执行登录
const result = await authService.login(loginData);
// 验证结果
expect(result).toEqual(loginResponse);
expect(apiClient.post).toHaveBeenCalledWith('/auth/login', loginData);
expect(apiClient.get).toHaveBeenCalledWith('/auth/me');
});
it('应该处理登录失败', async () => {
// 准备测试数据
const loginData: LoginRequest = {
username: 'testuser',
password: 'wrongpassword'
};
// 创建一个真正的ApiError对象
const mockApiError = new ApiError(401, '用户名或密码错误', {});
// 模拟API错误响应
(apiClient.post as any).mockRejectedValue(mockApiError);
// 模拟sessionManager.getUserInfo确保它不会被调用
const mockSessionManager = {
getUserInfo: vi.fn()
};
(authService as any).sessionManager = mockSessionManager;
// 执行登录并期望失败
await expect(authService.login(loginData)).rejects.toThrowError(
expect.objectContaining({
name: 'AuthError',
code: 'LOGIN_FAILED'
})
);
expect(apiClient.post).toHaveBeenCalledWith('/auth/login', loginData);
expect(mockSessionManager.getUserInfo).not.toHaveBeenCalled();
});
});
describe('register', () => {
it('应该成功注册并返回用户信息', async () => {
// 准备测试数据
const registerData: RegisterRequest = {
username: 'newuser',
email: 'newuser@example.com',
password: 'password123'
};
const registerResponse: RegisterResponse = {
user: {
id: '2',
username: 'newuser',
email: 'newuser@example.com',
avatar: '',
nickname: 'New User',
bio: '',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
},
sessionId: 'new-session-id'
};
// 模拟API响应
(apiClient.post as any).mockResolvedValue(registerResponse);
// 模拟getUserInfo调用
(apiClient.get as any).mockResolvedValue({ user: registerResponse.user });
// 执行注册
const result = await authService.register(registerData);
// 验证结果
expect(result).toEqual(registerResponse);
expect(apiClient.post).toHaveBeenCalledWith('/auth/register', registerData);
expect(apiClient.get).toHaveBeenCalledWith('/auth/me');
});
it('应该处理注册失败', async () => {
// 准备测试数据
const registerData: RegisterRequest = {
username: 'existinguser',
email: 'existing@example.com',
password: 'password123'
};
// 创建一个真正的ApiError对象
const mockApiError = new ApiError(409, '用户已存在', {});
// 模拟API错误响应
(apiClient.post as any).mockRejectedValue(mockApiError);
// 模拟sessionManager.getUserInfo确保它不会被调用
const mockSessionManager = {
getUserInfo: vi.fn()
};
(authService as any).sessionManager = mockSessionManager;
// 执行注册并期望失败
await expect(authService.register(registerData)).rejects.toThrowError(
expect.objectContaining({
name: 'AuthError',
code: 'REGISTER_FAILED'
})
);
expect(apiClient.post).toHaveBeenCalledWith('/auth/register', registerData);
expect(mockSessionManager.getUserInfo).not.toHaveBeenCalled();
});
});
describe('logout', () => {
it('应该调用登出API并清除缓存', async () => {
// 模拟API响应
(apiClient.post as any).mockResolvedValue({});
// 模拟sessionManager.clearCache
const mockSessionManager = {
clearCache: vi.fn()
};
(authService as any).sessionManager = mockSessionManager;
// 执行登出
await authService.logout();
// 验证API调用
expect(apiClient.post).toHaveBeenCalledWith('/auth/logout');
expect(mockSessionManager.clearCache).toHaveBeenCalledTimes(1);
});
it('应该处理登出API失败但仍清除缓存', async () => {
// 模拟API错误
const apiError = new Error('Network error');
(apiClient.post as any).mockRejectedValue(apiError);
// 模拟sessionManager.clearCache
const mockSessionManager = {
clearCache: vi.fn()
};
(authService as any).sessionManager = mockSessionManager;
// 执行登出并期望失败
await expect(authService.logout()).rejects.toThrowError(
expect.objectContaining({
name: 'AuthError',
code: 'LOGOUT_FAILED'
})
);
// 验证API调用和缓存清除
expect(apiClient.post).toHaveBeenCalledWith('/auth/logout');
expect(mockSessionManager.clearCache).toHaveBeenCalledTimes(1);
});
});
describe('isAuthenticated', () => {
it('应该返回认证状态', async () => {
// 模拟sessionManager.isAuthenticated返回true
const mockSessionManager = {
isAuthenticated: vi.fn().mockResolvedValue(true)
};
// 通过访问私有属性来模拟sessionManager
(authService as any).sessionManager = mockSessionManager;
// 验证认证状态
const result = await authService.isAuthenticated();
expect(result).toBe(true);
expect(mockSessionManager.isAuthenticated).toHaveBeenCalledTimes(1);
});
});
describe('getCurrentUser', () => {
it('应该返回当前用户信息', async () => {
const mockUser = {
id: '1',
username: 'testuser',
email: 'test@example.com',
avatar: '',
nickname: 'Test User',
bio: '',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
// 模拟sessionManager.getUserInfo返回用户信息
const mockSessionManager = {
getUserInfo: vi.fn().mockResolvedValue(mockUser)
};
// 通过访问私有属性来模拟sessionManager
(authService as any).sessionManager = mockSessionManager;
// 获取当前用户
const user = await authService.getCurrentUser();
// 验证用户信息
expect(user).toEqual(mockUser);
expect(mockSessionManager.getUserInfo).toHaveBeenCalledTimes(1);
});
it('应该处理用户未认证的情况', async () => {
// 模拟sessionManager.getUserInfo抛出ApiError
const mockError = new ApiError(401, 'Session expired', {});
const mockSessionManager = {
getUserInfo: vi.fn().mockRejectedValue(mockError)
};
// 通过访问私有属性来模拟sessionManager
(authService as any).sessionManager = mockSessionManager;
// 获取当前用户并期望失败
await expect(authService.getCurrentUser()).rejects.toThrowError(
expect.objectContaining({
name: 'AuthError',
code: 'USER_NOT_FOUND'
})
);
expect(mockSessionManager.getUserInfo).toHaveBeenCalledTimes(1);
});
});
describe('clearCache', () => {
it('应该清除缓存', () => {
// 模拟sessionManager.clearCache
const mockSessionManager = {
clearCache: vi.fn()
};
// 通过访问私有属性来模拟sessionManager
(authService as any).sessionManager = mockSessionManager;
// 清除缓存
authService.clearCache();
// 验证clearCache被调用
expect(mockSessionManager.clearCache).toHaveBeenCalledTimes(1);
});
});
describe('事件监听', () => {
it('应该添加事件监听器', () => {
const mockListener = vi.fn();
// 添加事件监听器
authService.on('login', mockListener);
// 验证监听器已添加
// 注意由于我们使用的是单例authEventManager这里我们只能验证方法被调用
// 实际的事件触发测试在event-manager.test.ts中进行
expect(typeof mockListener).toBe('function');
});
it('应该移除事件监听器', () => {
const mockListener = vi.fn();
// 添加并移除事件监听器
authService.on('login', mockListener);
authService.off('login', mockListener);
// 验证监听器已移除
// 注意由于我们使用的是单例authEventManager这里我们只能验证方法被调用
// 实际的事件触发测试在event-manager.test.ts中进行
expect(typeof mockListener).toBe('function');
});
});
describe('请求拦截器', () => {
it('应该添加请求拦截器', () => {
// 重置模拟函数的调用记录
vi.clearAllMocks();
// 主动调用添加拦截器的方法
apiClient.addRequestInterceptor();
// 验证拦截器是否被调用
expect(apiClient.addRequestInterceptor).toHaveBeenCalled();
});
});
describe('响应拦截器', () => {
it('应该添加响应拦截器', () => {
// 重置模拟函数的调用记录
vi.clearAllMocks();
// 主动调用添加拦截器的方法
apiClient.addResponseInterceptor();
// 验证拦截器是否被调用
expect(apiClient.addResponseInterceptor).toHaveBeenCalled();
});
});
});