Files
it/frontend/tests/unit/services/api.spec.js
XCool f25b0307db
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
初始化
2025-11-03 19:47:36 +08:00

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