354 lines
10 KiB
JavaScript
354 lines
10 KiB
JavaScript
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()
|
||
|
||
// 检查是否取消了所有请求
|
||
// 这里需要根据实际实现进行检查
|
||
})
|
||
})
|
||
}) |