1314 lines
36 KiB
Markdown
1314 lines
36 KiB
Markdown
# 前端 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<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 导入**
|
||
|
||
创建临时测试验证导入:
|
||
```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<BaseResponse>("post", "/login", { data });
|
||
};
|
||
export const register = (data: RegisterData) => {
|
||
return http.request<BaseResponse>("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?** |