Files
zsp-project/docs/superpowers/plans/2026-04-25-frontend-api-test-infra.md
2026-06-03 20:59:39 +08:00

36 KiB
Raw Blame History

前端 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.jsonscripts 中添加:

{
  "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.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?