36 KiB
前端 API 测试基础设施 实现计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 为 Vue3+TypeScript 前端建立 Vitest 测试框架,Mock Axios 测试 11 个 API 模块
Architecture: Vitest + vi.mock 模式,测试 API 层请求/响应类型验证,不依赖真实后端
Tech Stack: Vitest, @vitest/coverage-v8, jsdom, TypeScript
文件结构
| 文件 | 责任 |
|---|---|
vitest.config.ts |
Vitest 配置,环境、覆盖率、别名设置 |
package.json |
新增 vitest 依赖和测试脚本 |
src/api/test-utils.ts |
Mock 工具函数封装 |
src/api/__tests__/contract.spec.ts |
合同 API 测试 (15-20 cases) |
src/api/__tests__/zspContract.spec.ts |
中尚鹏合同测试 |
src/api/__tests__/zspFinances.spec.ts |
财务模块测试 |
src/api/__tests__/applyDelivery.spec.ts |
提货申请测试 |
src/api/__tests__/deliveryDetails.spec.ts |
出库明细测试 |
src/api/__tests__/inventory.spec.ts |
库存管理测试 |
src/api/__tests__/ownershipTransfer.spec.ts |
货权转移测试 |
src/api/__tests__/company.spec.ts |
公司信息测试 |
src/api/__tests__/user.spec.ts |
用户认证测试 |
src/api/__tests__/invoice.spec.ts |
发票测试 |
src/api/__tests__/settlement.spec.ts |
结算测试 |
src/api/__tests__/reconciliation.spec.ts |
对账单测试 |
Task 1: 基础设施配置
Files:
-
Create:
frontend/vitest.config.ts -
Modify:
frontend/package.json -
Step 1: 安装 Vitest 依赖
Run in frontend/:
pnpm add -D vitest @vitest/coverage-v8 jsdom @vitest/ui
Expected: 依赖安装成功
- Step 2: 创建 vitest.config.ts
// frontend/vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
include: ['src/api/__tests__/**/*.spec.ts'],
globals: true,
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'json'],
include: ['src/api/**/*.ts'],
exclude: ['src/api/__tests__/**', 'src/api/test-utils.ts', 'src/api/base.ts', 'src/api/utils.ts']
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
}
})
- Step 3: 更新 package.json 添加测试脚本
在 frontend/package.json 的 scripts 中添加:
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:ui": "vitest --ui"
}
}
- Step 4: 验证 Vitest 配置
Run:
cd frontend && pnpm test
Expected: Vitest 运行,报告 "No test files found" (正常,尚未创建测试)
- Step 5: 创建测试目录结构
mkdir -p frontend/src/api/__tests__
Task 2: Mock 工具函数
Files:
-
Create:
frontend/src/api/test-utils.ts -
Step 1: 创建 test-utils.ts
// frontend/src/api/test-utils.ts
import { vi } from 'vitest'
import type { BaseResponse } from './base'
/**
* Mock http.request 成功响应
*/
export function mockHttpSuccess<T>(data: T): BaseResponse<T> {
return {
success: true,
data,
message: 'ok'
}
}
/**
* Mock http.request 错误响应
*/
export function mockHttpError(message: string, code = 500): BaseResponse<null> {
return {
success: false,
data: null,
message
}
}
/**
* 创建 http.request 的 spy mock
* 注意:需要在每个测试文件中 import http 后使用
*/
export function createHttpMock() {
return {
spyOnRequest: (response: any) => {
const http = require('@/utils/http')
return vi.spyOn(http.http, 'request').mockResolvedValue(response)
},
spyOnError: (error: any) => {
const http = require('@/utils/http')
return vi.spyOn(http.http, 'request').mockRejectedValue(error)
}
}
}
- Step 2: 验证 test-utils 导入
创建临时测试验证导入:
// frontend/src/api/__tests__/test-utils.spec.ts
import { describe, it, expect } from 'vitest'
import { mockHttpSuccess, mockHttpError } from '../test-utils'
describe('test-utils', () => {
it('mockHttpSuccess 应返回正确结构', () => {
const result = mockHttpSuccess({ id: 1 })
expect(result.success).toBe(true)
expect(result.data).toEqual({ id: 1 })
})
it('mockHttpError 应返回错误结构', () => {
const result = mockHttpError('错误', 500)
expect(result.success).toBe(false)
expect(result.message).toBe('错误')
})
})
Run:
cd frontend && pnpm test
Expected: 2 tests pass
- Step 3: 删除临时测试,提交基础设施
rm frontend/src/api/__tests__/test-utils.spec.ts
cd frontend && git add vitest.config.ts package.json pnpm-lock.yaml src/api/test-utils.ts
git commit -m "feat(frontend): add vitest test infrastructure"
Task 3: Contract API 测试
Files:
-
Create:
frontend/src/api/__tests__/contract.spec.ts -
Step 1: 创建 contract.spec.ts 基础结构
// frontend/src/api/__tests__/contract.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
import type { BaseResponse } from '../base'
import {
getContractList,
getContract,
submitContract,
batchUploadContracts,
updateContract,
deleteContract,
getContractFolders,
createContractFolder,
deleteContractFolder,
getContractBatches,
createContractBatch,
deleteContractBatch
} from '../contract'
// Mock http.request
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
describe('Contract API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe('getContractList', () => {
it('应返回合同列表', async () => {
const mockData = [
{ id: 1, name: '合同1', contractCode: 'C001' },
{ id: 2, name: '合同2', contractCode: 'C002' }
]
vi.mocked(http.request).mockResolvedValue({
success: true,
data: mockData
})
const result = await getContractList()
expect(http.request).toHaveBeenCalledWith('get', '/api/contract', { params: undefined })
expect(result.success).toBe(true)
expect(result.data).toHaveLength(2)
})
it('带 contractType 参数应正确传递', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await getContractList({ contractType: 'business' })
expect(http.request).toHaveBeenCalledWith('get', '/api/contract', {
params: { contractType: 'business' }
})
})
})
describe('getContract', () => {
it('应返回单个合同详情', async () => {
const mockData = { id: 1, name: '合同详情', contractCode: 'C001' }
vi.mocked(http.request).mockResolvedValue({ success: true, data: mockData })
const result = await getContract(1)
expect(http.request).toHaveBeenCalledWith('get', '/api/contract/1')
expect(result.data.id).toBe(1)
})
})
describe('submitContract', () => {
it('应提交 FormData 格式数据', async () => {
const formData = new FormData()
formData.append('name', '新合同')
vi.mocked(http.request).mockResolvedValue({ success: true, message: 'ok' })
await submitContract(formData)
expect(http.request).toHaveBeenCalledWith('post', '/api/contract/upload', {
data: formData,
headers: { 'Content-Type': 'multipart/form-data' }
})
})
})
describe('batchUploadContracts', () => {
it('应批量上传合同', async () => {
const formData = new FormData()
vi.mocked(http.request).mockResolvedValue({
success: true,
data: { successCount: 5, failedItems: [] }
})
const result = await batchUploadContracts(formData)
expect(http.request).toHaveBeenCalledWith('post', '/api/contract/batch-upload', {
data: formData,
headers: { 'Content-Type': 'multipart/form-data' }
})
expect(result.data.successCount).toBe(5)
})
})
describe('updateContract', () => {
it('应 PUT 更新合同', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await updateContract(1, { name: '更新名称' })
expect(http.request).toHaveBeenCalledWith('put', '/api/contract/1', {
data: { name: '更新名称' }
})
})
})
describe('deleteContract', () => {
it('应 DELETE 删除合同', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await deleteContract(1)
expect(http.request).toHaveBeenCalledWith('delete', '/api/contract/1')
})
})
describe('Folder operations', () => {
it('getContractFolders 应返回文件夹列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await getContractFolders()
expect(http.request).toHaveBeenCalledWith('get', '/api/contract/folders')
})
it('createContractFolder 应 POST 创建文件夹', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: { id: 1 } })
await createContractFolder({ name: '新文件夹' })
expect(http.request).toHaveBeenCalledWith('post', '/api/contract/folders', { data: { name: '新文件夹' } })
})
it('deleteContractFolder 应 DELETE 文件夹', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await deleteContractFolder(1)
expect(http.request).toHaveBeenCalledWith('delete', '/api/contract/folders/1')
})
})
describe('Batch operations', () => {
it('getContractBatches 应返回批次列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await getContractBatches()
expect(http.request).toHaveBeenCalledWith('get', '/api/contract/batches', { params: undefined })
})
it('getContractBatches 带 folderId 参数', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await getContractBatches({ folderId: 1 })
expect(http.request).toHaveBeenCalledWith('get', '/api/contract/batches', { params: { folderId: 1 } })
})
})
})
- Step 2: 运行测试验证
Run:
cd frontend && pnpm test src/api/__tests__/contract.spec.ts
Expected: 所有测试通过
- Step 3: 提交
cd frontend && git add src/api/__tests__/contract.spec.ts
git commit -m "test(frontend): add contract API tests"
Task 4: User API 测试
Files:
-
Create:
frontend/src/api/__tests__/user.spec.ts -
Step 1: 读取 user.ts 了解 API 结构
// frontend/src/api/user.ts (参考)
export const login = (data: { username: string; password: string }) => {
return http.request<BaseResponse>("post", "/login", { data });
};
export const register = (data: RegisterData) => {
return http.request<BaseResponse>("post", "/auth/register", { data });
};
- Step 2: 创建 user.spec.ts
// frontend/src/api/__tests__/user.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
// 动态导入以应用 mock
const { login, register, getUser, refreshToken } = await import('../user')
describe('User API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe('login', () => {
it('应 POST 登录请求', async () => {
vi.mocked(http.request).mockResolvedValue({
success: true,
data: { accessToken: 'token123', refreshToken: 'refresh123' }
})
const result = await login({ username: 'test', password: '123456' })
expect(http.request).toHaveBeenCalledWith('post', '/login', {
data: { username: 'test', password: '123456' }
})
expect(result.success).toBe(true)
})
})
describe('register', () => {
it('应 POST 注册请求', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await register({ username: 'newuser', email: 'test@example.com', password: '123456' })
expect(http.request).toHaveBeenCalledWith('post', '/auth/register', {
data: { username: 'newuser', email: 'test@example.com', password: '123456' }
})
})
})
describe('getUser', () => {
it('应 GET 用户信息', async () => {
vi.mocked(http.request).mockResolvedValue({
success: true,
data: { id: 1, username: 'test', email: 'test@example.com' }
})
const result = await getUser(1)
expect(http.request).toHaveBeenCalledWith('get', '/api/users/1')
expect(result.data.username).toBe('test')
})
})
describe('refreshToken', () => {
it('应 POST 刷新 token', async () => {
vi.mocked(http.request).mockResolvedValue({
success: true,
data: { accessToken: 'newtoken', refreshToken: 'newrefresh' }
})
await refreshToken({ refreshToken: 'oldrefresh' })
expect(http.request).toHaveBeenCalledWith('post', '/refresh-token', {
data: { refreshToken: 'oldrefresh' }
})
})
})
})
- Step 3: 运行测试
cd frontend && pnpm test src/api/__tests__/user.spec.ts
- Step 4: 提交
git add src/api/__tests__/user.spec.ts
git commit -m "test(frontend): add user API tests"
Task 5: ZSP Contract API 测试
Files:
-
Create:
frontend/src/api/__tests__/zspContract.spec.ts -
Step 1: 创建 zspContract.spec.ts
// frontend/src/api/__tests__/zspContract.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
const api = await import('../zspContract')
describe('ZSP Contract API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe('Folder operations', () => {
it('getFolders 应返回文件夹列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.getFolders()
expect(http.request).toHaveBeenCalledWith('get', '/api/zsp-contract/folders')
})
it('createFolder 应创建文件夹', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.createFolder({ name: '新文件夹', parentId: null })
expect(http.request).toHaveBeenCalledWith('post', '/api/zsp-contract/folders', {
data: { name: '新文件夹', parentId: null }
})
})
})
describe('Contract operations', () => {
it('getContracts 应返回合同列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.getContracts()
expect(http.request).toHaveBeenCalledWith('get', '/api/zsp-contract/contracts')
})
it('getContractDetail 应返回合同详情', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: { id: 1 } })
await api.getContractDetail(1)
expect(http.request).toHaveBeenCalledWith('get', '/api/zsp-contract/contracts/1')
})
it('uploadContract 应上传合同文件', async () => {
const formData = new FormData()
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.uploadContract(formData)
expect(http.request).toHaveBeenCalledWith('post', '/api/zsp-contract/contracts', {
data: formData,
headers: { 'Content-Type': 'multipart/form-data' }
})
})
})
describe('Detail operations', () => {
it('getContractDetails 应返回明细列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.getContractDetails(1)
expect(http.request).toHaveBeenCalledWith('get', '/api/zsp-contract/contracts/1/details')
})
it('importDetailsFromExcel 应导入 Excel', async () => {
const formData = new FormData()
vi.mocked(http.request).mockResolvedValue({ success: true, data: { count: 10 } })
await api.importDetailsFromExcel(1, formData)
expect(http.request).toHaveBeenCalledWith('post', '/api/zsp-contract/contracts/1/details/import', {
data: formData,
headers: { 'Content-Type': 'multipart/form-data' }
})
})
it('exportDetailsToExcel 应导出 Excel', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.exportDetailsToExcel(1)
expect(http.request).toHaveBeenCalledWith('get', '/api/zsp-contract/contracts/1/details/export')
})
})
})
- Step 2: 运行测试
cd frontend && pnpm test src/api/__tests__/zspContract.spec.ts
- Step 3: 提交
git add src/api/__tests__/zspContract.spec.ts
git commit -m "test(frontend): add zspContract API tests"
Task 6: ZSP Finances API 测试
Files:
-
Create:
frontend/src/api/__tests__/zspFinances.spec.ts -
Step 1: 创建 zspFinances.spec.ts
// frontend/src/api/__tests__/zspFinances.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
const api = await import('../zspFinances')
describe('ZSP Finances API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
// 货代费用
describe('Freight Charges', () => {
it('listFreightCharges 应返回列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.listFreightCharges()
expect(http.request).toHaveBeenCalledWith('get', '/api/zsp-finances/freight-charges')
})
it('createFreightCharge 应创建记录', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.createFreightCharge({ blNumber: 'BL001', amount: 1000 })
expect(http.request).toHaveBeenCalledWith('post', '/api/zsp-finances/freight-charges', {
data: { blNumber: 'BL001', amount: 1000 }
})
})
})
// 杂费
describe('Misc Charges', () => {
it('listMiscCharges 应返回列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.listMiscCharges()
expect(http.request).toHaveBeenCalledWith('get', '/api/zsp-finances/misc-charges')
})
})
// 服务费
describe('Service Fees', () => {
it('listServiceFees 应返回列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.listServiceFees()
expect(http.request).toHaveBeenCalledWith('get', '/api/zsp-finances/service-fees')
})
})
// 成本构成
describe('Cost Breakdowns', () => {
it('listCostBreakdowns 应返回列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.listCostBreakdowns()
expect(http.request).toHaveBeenCalledWith('get', '/api/zsp-finances/cost-breakdowns')
})
})
// 利润记录
describe('Profit Records', () => {
it('listProfitRecords 应返回列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.listProfitRecords()
expect(http.request).toHaveBeenCalledWith('get', '/api/zsp-finances/profit-records')
})
})
})
- Step 2: 运行测试
cd frontend && pnpm test src/api/__tests__/zspFinances.spec.ts
- Step 3: 提交
git add src/api/__tests__/zspFinances.spec.ts
git commit -m "test(frontend): add zspFinances API tests"
Task 7: Apply Delivery API 测试
Files:
-
Create:
frontend/src/api/__tests__/applyDelivery.spec.ts -
Step 1: 创建 applyDelivery.spec.ts
// frontend/src/api/__tests__/applyDelivery.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
const api = await import('../applyDelivery')
describe('Apply Delivery API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('listApplyDelivery 应返回提货申请列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: { items: [], total: 0 } })
await api.listApplyDelivery()
expect(http.request).toHaveBeenCalledWith('get', '/api/apply-delivery/list')
})
it('getApplyDelivery 应返回详情', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: { id: 1 } })
await api.getApplyDelivery(1)
expect(http.request).toHaveBeenCalledWith('get', '/api/apply-delivery/detail/1')
})
it('createApplyDelivery 应创建申请', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: { id: 1 } })
await api.createApplyDelivery({ blNumber: 'BL001' })
expect(http.request).toHaveBeenCalledWith('post', '/api/apply-delivery/create', {
data: { blNumber: 'BL001' }
})
})
it('updateApplyDelivery 应更新申请', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.updateApplyDelivery(1, { status: 'approved' })
expect(http.request).toHaveBeenCalledWith('put', '/api/apply-delivery/update/1', {
data: { status: 'approved' }
})
})
it('cancelApplyDelivery 应取消申请', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.cancelApplyDelivery(1)
expect(http.request).toHaveBeenCalledWith('put', '/api/apply-delivery/cancel/1')
})
it('deleteApplyDelivery 应删除申请', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.deleteApplyDelivery(1)
expect(http.request).toHaveBeenCalledWith('delete', '/api/apply-delivery/1')
})
})
- Step 2: 运行测试
cd frontend && pnpm test src/api/__tests__/applyDelivery.spec.ts
- Step 3: 提交
git add src/api/__tests__/applyDelivery.spec.ts
git commit -m "test(frontend): add applyDelivery API tests"
Task 8: Delivery Details API 测试
Files:
-
Create:
frontend/src/api/__tests__/deliveryDetails.spec.ts -
Step 1: 创建 deliveryDetails.spec.ts
// frontend/src/api/__tests__/deliveryDetails.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
const api = await import('../deliveryDetails')
describe('Delivery Details API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('listDeliveryDetails 应返回出库明细列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: { items: [], total: 0 } })
await api.listDeliveryDetails()
expect(http.request).toHaveBeenCalledWith('get', '/api/delivery-details/list')
})
it('getDeliveryDetail 应返回详情', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: { id: 1 } })
await api.getDeliveryDetail(1)
expect(http.request).toHaveBeenCalledWith('get', '/api/delivery-details/detail/1')
})
it('createDeliveryDetail 应创建出库单', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.createDeliveryDetail({ quantity: 100 })
expect(http.request).toHaveBeenCalledWith('post', '/api/delivery-details/create', {
data: { quantity: 100 }
})
})
it('updateDeliveryDetail 应更新出库单', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.updateDeliveryDetail(1, { quantity: 200 })
expect(http.request).toHaveBeenCalledWith('put', '/api/delivery-details/update/1', {
data: { quantity: 200 }
})
})
it('deleteDeliveryDetail 应删除出库单', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.deleteDeliveryDetail(1)
expect(http.request).toHaveBeenCalledWith('delete', '/api/delivery-details/1')
})
})
- Step 2: 运行测试
cd frontend && pnpm test src/api/__tests__/deliveryDetails.spec.ts
- Step 3: 提交
git add src/api/__tests__/deliveryDetails.spec.ts
git commit -m "test(frontend): add deliveryDetails API tests"
Task 9: Inventory API 测试
Files:
-
Create:
frontend/src/api/__tests__/inventory.spec.ts -
Step 1: 创建 inventory.spec.ts
// frontend/src/api/__tests__/inventory.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
const api = await import('../inventory')
describe('Inventory API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe('Warehouse', () => {
it('listWarehouses 应返回仓库列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.listWarehouses()
expect(http.request).toHaveBeenCalledWith('get', '/api/inventory/warehouses')
})
it('createWarehouse 应创建仓库', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.createWarehouse({ name: '新仓库' })
expect(http.request).toHaveBeenCalledWith('post', '/api/inventory/warehouses/create', {
data: { name: '新仓库' }
})
})
})
describe('Inbound', () => {
it('listInbound 应返回入库列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: { items: [], total: 0 } })
await api.listInbound()
expect(http.request).toHaveBeenCalledWith('get', '/api/inventory/inbound/list')
})
it('createInbound 应创建入库记录', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.createInbound({ warehouse: '仓库A', commodity: '大豆' })
expect(http.request).toHaveBeenCalledWith('post', '/api/inventory/inbound/create', {
data: { warehouse: '仓库A', commodity: '大豆' }
})
})
})
describe('Summary', () => {
it('inventorySummary 应返回库存汇总', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.inventorySummary()
expect(http.request).toHaveBeenCalledWith('get', '/api/inventory/summary')
})
it('inventorySummary 带筛选参数', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.inventorySummary({ warehouse: '仓库A' })
expect(http.request).toHaveBeenCalledWith('get', '/api/inventory/summary', {
params: { warehouse: '仓库A' }
})
})
})
})
- Step 2: 运行测试
cd frontend && pnpm test src/api/__tests__/inventory.spec.ts
- Step 3: 提交
git add src/api/__tests__/inventory.spec.ts
git commit -m "test(frontend): add inventory API tests"
Task 10: Ownership Transfer API 测试
Files:
-
Create:
frontend/src/api/__tests__/ownershipTransfer.spec.ts -
Step 1: 创建 ownershipTransfer.spec.ts
// frontend/src/api/__tests__/ownershipTransfer.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
const api = await import('../ownershipTransfer')
describe('Ownership Transfer API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('listOwnershipTransfers 应返回列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: { items: [], total: 0 } })
await api.listOwnershipTransfers()
expect(http.request).toHaveBeenCalledWith('get', '/api/ownership-transfer/list')
})
it('getOwnershipTransfer 应返回详情', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: { id: 1 } })
await api.getOwnershipTransfer(1)
expect(http.request).toHaveBeenCalledWith('get', '/api/ownership-transfer/detail/1')
})
it('createOwnershipTransfer 应创建货权转移', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.createOwnershipTransfer({ transferor: '公司A', transferee: '公司B' })
expect(http.request).toHaveBeenCalledWith('post', '/api/ownership-transfer/create', {
data: { transferor: '公司A', transferee: '公司B' }
})
})
it('createOwnershipTransferWithFiles 应带文件创建', async () => {
const formData = new FormData()
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.createOwnershipTransferWithFiles(formData)
expect(http.request).toHaveBeenCalledWith('post', '/api/ownership-transfer/create-with-files', {
data: formData,
headers: { 'Content-Type': 'multipart/form-data' }
})
})
it('deleteOwnershipTransfer 应删除', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.deleteOwnershipTransfer(1)
expect(http.request).toHaveBeenCalledWith('delete', '/api/ownership-transfer/1')
})
})
- Step 2: 运行测试
cd frontend && pnpm test src/api/__tests__/ownershipTransfer.spec.ts
- Step 3: 提交
git add src/api/__tests__/ownershipTransfer.spec.ts
git commit -m "test(frontend): add ownershipTransfer API tests"
Task 11: Company API 测试
Files:
-
Create:
frontend/src/api/__tests__/company.spec.ts -
Step 1: 创建 company.spec.ts
// frontend/src/api/__tests__/company.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
const api = await import('../company')
describe('Company API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe('Folders', () => {
it('listFolders 应返回文件夹列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.listFolders()
expect(http.request).toHaveBeenCalledWith('get', '/api/company/folders')
})
it('createFolder 应创建文件夹', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.createFolder({ name: '新文件夹' })
expect(http.request).toHaveBeenCalledWith('post', '/api/company/folders', {
data: { name: '新文件夹' }
})
})
})
describe('Files', () => {
it('listFiles 应返回文件列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.listFiles()
expect(http.request).toHaveBeenCalledWith('get', '/api/company/files')
})
it('uploadFiles 应上传文件', async () => {
const formData = new FormData()
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.uploadFiles(formData)
expect(http.request).toHaveBeenCalledWith('post', '/api/company/files/upload', {
data: formData,
headers: { 'Content-Type': 'multipart/form-data' }
})
})
})
})
- Step 2: 运行测试
cd frontend && pnpm test src/api/__tests__/company.spec.ts
- Step 3: 提交
git add src/api/__tests__/company.spec.ts
git commit -m "test(frontend): add company API tests"
Task 12: Invoice API 测试
Files:
-
Create:
frontend/src/api/__tests__/invoice.spec.ts -
Step 1: 创建 invoice.spec.ts
// frontend/src/api/__tests__/invoice.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
const api = await import('../invoice')
describe('Invoice API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('listInvoices 应返回发票列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.listInvoices()
expect(http.request).toHaveBeenCalled()
})
it('createInvoice 应创建发票', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.createInvoice({ invoiceNumber: 'INV001', amount: 10000 })
expect(http.request).toHaveBeenCalled()
})
it('updateInvoice 应更新发票', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.updateInvoice(1, { amount: 20000 })
expect(http.request).toHaveBeenCalled()
})
it('deleteInvoice 应删除发票', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.deleteInvoice(1)
expect(http.request).toHaveBeenCalled()
})
})
- Step 2: 运行测试
cd frontend && pnpm test src/api/__tests__/invoice.spec.ts
- Step 3: 提交
git add src/api/__tests__/invoice.spec.ts
git commit -m "test(frontend): add invoice API tests"
Task 13: Settlement & Reconciliation API 测试
Files:
-
Create:
frontend/src/api/__tests__/settlement.spec.ts -
Create:
frontend/src/api/__tests__/reconciliation.spec.ts -
Step 1: 创建 settlement.spec.ts
// frontend/src/api/__tests__/settlement.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
const api = await import('../settlement')
describe('Settlement API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('listSettlements 应返回结算单列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.listSettlements()
expect(http.request).toHaveBeenCalled()
})
it('createSettlement 应创建结算单', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.createSettlement({ contractCode: 'C001' })
expect(http.request).toHaveBeenCalled()
})
})
- Step 2: 创建 reconciliation.spec.ts
// frontend/src/api/__tests__/reconciliation.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { http } from '@/utils/http'
vi.mock('@/utils/http', () => ({
http: {
request: vi.fn()
}
}))
const api = await import('../reconciliation')
describe('Reconciliation API', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('listReconciliations 应返回对账单列表', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true, data: [] })
await api.listReconciliations()
expect(http.request).toHaveBeenCalled()
})
it('createReconciliation 应创建对账单', async () => {
vi.mocked(http.request).mockResolvedValue({ success: true })
await api.createReconciliation({ companyName: '公司A' })
expect(http.request).toHaveBeenCalled()
})
})
- Step 3: 运行测试
cd frontend && pnpm test src/api/__tests__/settlement.spec.ts src/api/__tests__/reconciliation.spec.ts
- Step 4: 提交
git add src/api/__tests__/settlement.spec.ts src/api/__tests__/reconciliation.spec.ts
git commit -m "test(frontend): add settlement and reconciliation API tests"
Task 14: 运行完整测试套件并验证覆盖率
Files:
-
Run tests
-
Step 1: 运行所有 API 测试
cd frontend && pnpm test
Expected: 所有测试通过
- Step 2: 运行覆盖率报告
cd frontend && pnpm test:coverage
Expected: 覆盖率 > 80%
- Step 3: 查看覆盖率 HTML 报告
open frontend/coverage/index.html
- Step 4: 最终提交
git add -A
git commit -m "feat(frontend): complete API test infrastructure with vitest"
Self-Review
Spec Coverage Check
| Spec Requirement | Task |
|---|---|
| Vitest 配置 | Task 1 |
| package.json 脚本 | Task 1 |
| Mock 工具函数 | Task 2 |
| 11 API 模块测试 | Tasks 3-13 |
| 覆盖率报告 | Task 14 |
Placeholder Scan
- 无 TBD/TODO 占位符
- 所有代码步骤包含完整实现
- 所有命令包含预期输出
Type Consistency
http.requestmock 在所有测试文件中一致使用vi.mock('@/utils/http')- 响应结构统一为
{ success: boolean, data?: any, message?: string }
Plan complete and saved to docs/superpowers/plans/2026-04-25-frontend-api-test-infra.md. Two execution options:
1. Subagent-Driven (recommended) - I dispatch a fresh subagent per task, review between tasks, fast iteration
2. Inline Execution - Execute tasks in this session using executing-plans, batch execution with checkpoints
Which approach?