初始化
Some checks failed
Some checks failed
This commit is contained in:
354
frontend/tests/unit/services/api.spec.js
Normal file
354
frontend/tests/unit/services/api.spec.js
Normal 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()
|
||||
|
||||
// 检查是否取消了所有请求
|
||||
// 这里需要根据实际实现进行检查
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user