# 前端 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/`: ```bash pnpm add -D vitest @vitest/coverage-v8 jsdom @vitest/ui ``` Expected: 依赖安装成功 - [ ] **Step 2: 创建 vitest.config.ts** ```typescript // 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` 中添加: ```json { "scripts": { "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", "test:ui": "vitest --ui" } } ``` - [ ] **Step 4: 验证 Vitest 配置** Run: ```bash cd frontend && pnpm test ``` Expected: Vitest 运行,报告 "No test files found" (正常,尚未创建测试) - [ ] **Step 5: 创建测试目录结构** ```bash mkdir -p frontend/src/api/__tests__ ``` --- ### Task 2: Mock 工具函数 **Files:** - Create: `frontend/src/api/test-utils.ts` - [ ] **Step 1: 创建 test-utils.ts** ```typescript // frontend/src/api/test-utils.ts import { vi } from 'vitest' import type { BaseResponse } from './base' /** * Mock http.request 成功响应 */ export function mockHttpSuccess(data: T): BaseResponse { return { success: true, data, message: 'ok' } } /** * Mock http.request 错误响应 */ export function mockHttpError(message: string, code = 500): BaseResponse { 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 导入** 创建临时测试验证导入: ```typescript // 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: ```bash cd frontend && pnpm test ``` Expected: 2 tests pass - [ ] **Step 3: 删除临时测试,提交基础设施** ```bash 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 基础结构** ```typescript // 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: ```bash cd frontend && pnpm test src/api/__tests__/contract.spec.ts ``` Expected: 所有测试通过 - [ ] **Step 3: 提交** ```bash 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 结构** ```typescript // frontend/src/api/user.ts (参考) export const login = (data: { username: string; password: string }) => { return http.request("post", "/login", { data }); }; export const register = (data: RegisterData) => { return http.request("post", "/auth/register", { data }); }; ``` - [ ] **Step 2: 创建 user.spec.ts** ```typescript // 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: 运行测试** ```bash cd frontend && pnpm test src/api/__tests__/user.spec.ts ``` - [ ] **Step 4: 提交** ```bash 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** ```typescript // 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: 运行测试** ```bash cd frontend && pnpm test src/api/__tests__/zspContract.spec.ts ``` - [ ] **Step 3: 提交** ```bash 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** ```typescript // 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: 运行测试** ```bash cd frontend && pnpm test src/api/__tests__/zspFinances.spec.ts ``` - [ ] **Step 3: 提交** ```bash 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** ```typescript // 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: 运行测试** ```bash cd frontend && pnpm test src/api/__tests__/applyDelivery.spec.ts ``` - [ ] **Step 3: 提交** ```bash 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** ```typescript // 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: 运行测试** ```bash cd frontend && pnpm test src/api/__tests__/deliveryDetails.spec.ts ``` - [ ] **Step 3: 提交** ```bash 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** ```typescript // 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: 运行测试** ```bash cd frontend && pnpm test src/api/__tests__/inventory.spec.ts ``` - [ ] **Step 3: 提交** ```bash 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** ```typescript // 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: 运行测试** ```bash cd frontend && pnpm test src/api/__tests__/ownershipTransfer.spec.ts ``` - [ ] **Step 3: 提交** ```bash 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** ```typescript // 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: 运行测试** ```bash cd frontend && pnpm test src/api/__tests__/company.spec.ts ``` - [ ] **Step 3: 提交** ```bash 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** ```typescript // 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: 运行测试** ```bash cd frontend && pnpm test src/api/__tests__/invoice.spec.ts ``` - [ ] **Step 3: 提交** ```bash 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** ```typescript // 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** ```typescript // 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: 运行测试** ```bash cd frontend && pnpm test src/api/__tests__/settlement.spec.ts src/api/__tests__/reconciliation.spec.ts ``` - [ ] **Step 4: 提交** ```bash 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 测试** ```bash cd frontend && pnpm test ``` Expected: 所有测试通过 - [ ] **Step 2: 运行覆盖率报告** ```bash cd frontend && pnpm test:coverage ``` Expected: 覆盖率 > 80% - [ ] **Step 3: 查看覆盖率 HTML 报告** ```bash open frontend/coverage/index.html ``` - [ ] **Step 4: 最终提交** ```bash 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.request` mock 在所有测试文件中一致使用 `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?**