初始化
Some checks failed
CI/CD Pipeline / 测试 (18.x) (push) Has been cancelled
CI/CD Pipeline / 测试 (20.x) (push) Has been cancelled
CI/CD Pipeline / 安全检查 (push) Has been cancelled
CI/CD Pipeline / 部署 (push) Has been cancelled
CI/CD Pipeline / 通知 (push) Has been cancelled

This commit is contained in:
2025-11-03 19:47:36 +08:00
parent 7a04b85667
commit f25b0307db
454 changed files with 37064 additions and 4544 deletions

View File

@@ -0,0 +1,354 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { api, clearCache, cancelAllRequests } from '@/services/api'
import axios from 'axios'
// 模拟axios
vi.mock('axios')
const mockedAxios = vi.mocked(axios)
// 模拟localStorage
const localStorageMock = {
getItem: vi.fn(),
setItem: vi.fn(),
removeItem: vi.fn(),
clear: vi.fn()
}
global.localStorage = localStorageMock
// 模拟console方法
const consoleSpy = {
error: vi.spyOn(console, 'error').mockImplementation(() => {}),
warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
log: vi.spyOn(console, 'log').mockImplementation(() => {})
}
// 模拟window.performance
Object.defineProperty(window, 'performance', {
value: {
now: vi.fn(() => Date.now())
}
})
describe('API Service', () => {
beforeEach(() => {
// 重置所有模拟
vi.clearAllMocks()
// 模拟axios.create返回值
mockedAxios.create.mockReturnValue({
request: vi.fn(),
interceptors: {
request: {
use: vi.fn()
},
response: {
use: vi.fn()
}
}
})
})
afterEach(() => {
// 清理所有请求
cancelAllRequests()
clearCache()
})
describe('API实例', () => {
it('应该正确创建API实例', () => {
expect(api).toBeDefined()
expect(typeof api.get).toBe('function')
expect(typeof api.post).toBe('function')
expect(typeof api.put).toBe('function')
expect(typeof api.delete).toBe('function')
expect(typeof api.request).toBe('function')
})
it('应该设置正确的baseURL', () => {
// 由于我们模拟了axios.create这里我们检查调用参数
expect(mockedAxios.create).toHaveBeenCalledWith(
expect.objectContaining({
baseURL: 'https://localhost:7001/api',
timeout: 15000
})
)
})
})
describe('请求拦截器', () => {
it('应该在请求前添加时间戳', async () => {
// 模拟请求
const mockRequest = {
url: '/test',
method: 'get',
headers: {}
}
// 模拟请求拦截器
const requestInterceptor = mockedAxios.create().interceptors.request.use.mock.calls[0][0]
const processedRequest = requestInterceptor(mockRequest)
// 检查是否添加了时间戳
expect(processedRequest.metadata).toBeDefined()
expect(processedRequest.metadata.startTime).toBeDefined()
})
it('应该在请求前添加CSRF令牌', async () => {
// 模拟localStorage中的CSRF令牌
localStorageMock.getItem.mockReturnValue('test-csrf-token')
// 模拟请求
const mockRequest = {
url: '/test',
method: 'post',
headers: {}
}
// 模拟请求拦截器
const requestInterceptor = mockedAxios.create().interceptors.request.use.mock.calls[0][0]
const processedRequest = requestInterceptor(mockRequest)
// 检查是否添加了CSRF令牌
expect(processedRequest.headers['X-CSRF-Token']).toBe('test-csrf-token')
})
it('应该在请求前添加认证令牌', async () => {
// 模拟localStorage中的认证令牌
localStorageMock.getItem.mockImplementation((key) => {
if (key === 'auth_token') return 'test-auth-token'
return null
})
// 模拟请求
const mockRequest = {
url: '/test',
method: 'get',
headers: {}
}
// 模拟请求拦截器
const requestInterceptor = mockedAxios.create().interceptors.request.use.mock.calls[0][0]
const processedRequest = requestInterceptor(mockRequest)
// 检查是否添加了认证令牌
expect(processedRequest.headers.Authorization).toBe('Bearer test-auth-token')
})
})
describe('响应拦截器', () => {
it('应该正确处理成功响应', async () => {
// 模拟响应
const mockResponse = {
data: { success: true, data: { id: 1, name: 'Test' } },
status: 200,
statusText: 'OK',
headers: {},
config: {
url: '/test',
method: 'get',
metadata: { startTime: Date.now() - 100 }
}
}
// 模拟响应拦截器
const responseInterceptor = mockedAxios.create().interceptors.response.use.mock.calls[0][0]
const processedResponse = responseInterceptor(mockResponse)
// 检查响应处理
expect(processedResponse.data).toEqual({ success: true, data: { id: 1, name: 'Test' } })
})
it('应该记录请求耗时', async () => {
// 模拟响应
const mockResponse = {
data: { success: true, data: { id: 1, name: 'Test' } },
status: 200,
statusText: 'OK',
headers: {},
config: {
url: '/test',
method: 'get',
metadata: { startTime: Date.now() - 100 }
}
}
// 模拟响应拦截器
const responseInterceptor = mockedAxios.create().interceptors.response.use.mock.calls[0][0]
responseInterceptor(mockResponse)
// 检查是否记录了请求耗时
expect(consoleSpy.log).toHaveBeenCalledWith(
expect.stringContaining('请求完成'),
expect.anything()
)
})
it('应该缓存GET请求响应', async () => {
// 模拟响应
const mockResponse = {
data: { success: true, data: { id: 1, name: 'Test' } },
status: 200,
statusText: 'OK',
headers: {},
config: {
url: '/test',
method: 'get',
metadata: { startTime: Date.now() - 100 }
}
}
// 模拟响应拦截器
const responseInterceptor = mockedAxios.create().interceptors.response.use.mock.calls[0][0]
responseInterceptor(mockResponse)
// 检查是否缓存了响应
expect(localStorageMock.setItem).toHaveBeenCalledWith(
expect.stringContaining('api_cache_'),
expect.any(String)
)
})
})
describe('错误处理', () => {
it('应该正确处理网络错误', async () => {
// 模拟网络错误
const networkError = new Error('Network Error')
networkError.code = 'NETWORK_ERROR'
// 模拟错误拦截器
const errorInterceptor = mockedAxios.create().interceptors.response.use.mock.calls[0][1]
// 处理错误
const result = errorInterceptor(networkError)
// 检查错误处理
expect(result).rejects.toThrow()
expect(consoleSpy.error).toHaveBeenCalledWith(
'网络错误',
expect.any(Error)
)
})
it('应该正确处理超时错误', async () => {
// 模拟超时错误
const timeoutError = new Error('Timeout')
timeoutError.code = 'ECONNABORTED'
// 模拟错误拦截器
const errorInterceptor = mockedAxios.create().interceptors.response.use.mock.calls[0][1]
// 处理错误
const result = errorInterceptor(timeoutError)
// 检查错误处理
expect(result).rejects.toThrow()
expect(consoleSpy.error).toHaveBeenCalledWith(
'请求超时',
expect.any(Error)
)
})
it('应该正确处理服务器错误', async () => {
// 模拟服务器错误响应
const serverError = {
response: {
status: 500,
statusText: 'Internal Server Error',
data: { message: '服务器内部错误' }
}
}
// 模拟错误拦截器
const errorInterceptor = mockedAxios.create().interceptors.response.use.mock.calls[0][1]
// 处理错误
const result = errorInterceptor(serverError)
// 检查错误处理
expect(result).rejects.toThrow()
expect(consoleSpy.error).toHaveBeenCalledWith(
'服务器错误',
expect.any(Object)
)
})
it('应该正确处理401未授权错误', async () => {
// 模拟401错误响应
const unauthorizedError = {
response: {
status: 401,
statusText: 'Unauthorized',
data: { message: '未授权访问' }
}
}
// 模拟错误拦截器
const errorInterceptor = mockedAxios.create().interceptors.response.use.mock.calls[0][1]
// 处理错误
const result = errorInterceptor(unauthorizedError)
// 检查错误处理
expect(result).rejects.toThrow()
expect(consoleSpy.error).toHaveBeenCalledWith(
'未授权访问',
expect.any(Object)
)
})
})
describe('缓存管理', () => {
it('应该正确缓存GET请求', () => {
// 模拟缓存数据
const cacheKey = 'api_cache_/test'
const cacheData = {
data: { id: 1, name: 'Test' },
timestamp: Date.now(),
expiry: Date.now() + 300000 // 5分钟后过期
}
// 设置缓存
localStorageMock.setItem.mockReturnValue()
localStorageMock.getItem.mockReturnValue(JSON.stringify(cacheData))
// 检查缓存设置
localStorageMock.setItem(cacheKey, JSON.stringify(cacheData))
expect(localStorageMock.setItem).toHaveBeenCalledWith(cacheKey, JSON.stringify(cacheData))
})
it('应该正确清除缓存', () => {
// 调用清除缓存函数
clearCache()
// 检查是否清除了所有缓存
expect(localStorageMock.removeItem).toHaveBeenCalledWith('api_cache_/test')
})
})
describe('请求取消', () => {
it('应该正确取消重复请求', () => {
// 模拟请求配置
const requestConfig = {
url: '/test',
method: 'get'
}
// 模拟CancelToken
const cancelToken = {
token: 'test-token',
cancel: vi.fn()
}
// 检查是否创建了CancelToken
expect(cancelToken.token).toBe('test-token')
})
it('应该正确取消所有请求', () => {
// 调用取消所有请求函数
cancelAllRequests()
// 检查是否取消了所有请求
// 这里需要根据实际实现进行检查
})
})
})