test: 添加前端 API 单元测试

- 添加 vitest 测试框架配置
- 添加 src/api/__tests__/api.test.ts: 16个 API 测试
- 修复 points.ts 和 ledger.ts 导入问题

测试覆盖:
- AccountAPI: 物理账户和子账户方法
- TransactionAPI: transfer/deposit/withdraw
- LedgerAPI: subjects/entry/accountEntries
- ReconciliationAPI: 8个端点方法
- PointsAPI: 5个端点方法
- 类型定义和导出测试
This commit is contained in:
tangweijie 2026-01-05 18:39:58 +08:00
parent 4086cc00de
commit 4b78c43d42
5 changed files with 217 additions and 4 deletions

View File

@ -8,7 +8,10 @@
"dev:mock": "VITE_USE_MOCK=true vite", "dev:mock": "VITE_USE_MOCK=true vite",
"build": "vue-tsc && vite build", "build": "vue-tsc && vite build",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}, },
"dependencies": { "dependencies": {
"vue": "^3.4.21", "vue": "^3.4.21",
@ -25,7 +28,10 @@
"vite": "^5.1.4", "vite": "^5.1.4",
"vue-tsc": "^2.0.6", "vue-tsc": "^2.0.6",
"msw": "^2.2.1", "msw": "^2.2.1",
"@types/node": "^20.11.19" "@types/node": "^20.11.19",
"vitest": "^1.3.1",
"@vitest/coverage-v8": "^1.3.1",
"happy-dom": "^13.6.2"
}, },
"msw": { "msw": {
"workerDirectory": [ "workerDirectory": [

View File

@ -0,0 +1,183 @@
/**
* API
* API
*/
import { describe, it, expect, vi } from 'vitest'
// Mock axios
vi.mock('axios', () => ({
default: {
create: vi.fn(() => ({
interceptors: {
request: { use: vi.fn() },
response: { use: vi.fn() }
},
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn()
}))
}
}))
// ==================== Account API 测试 ====================
describe('AccountAPI', () => {
it('should export AccountAPI class', async () => {
const { AccountAPI } = await import('../account')
expect(AccountAPI).toBeDefined()
})
it('should have physical account methods', async () => {
const { AccountAPI } = await import('../account')
// 实体账户相关方法
expect(typeof AccountAPI.createPhysicalAccount).toBe('function')
expect(typeof AccountAPI.getPhysicalAccounts).toBe('function')
expect(typeof AccountAPI.getPhysicalAccount).toBe('function')
expect(typeof AccountAPI.freezePhysicalAccount).toBe('function')
expect(typeof AccountAPI.unfreezePhysicalAccount).toBe('function')
})
it('should have sub account methods', async () => {
const { AccountAPI } = await import('../account')
// 虚拟子账户相关方法
expect(typeof AccountAPI.createSubAccount).toBe('function')
expect(typeof AccountAPI.getSubAccount).toBe('function')
expect(typeof AccountAPI.getSubAccountBalance).toBe('function')
expect(typeof AccountAPI.freezeSubAccount).toBe('function')
expect(typeof AccountAPI.unfreezeSubAccount).toBe('function')
expect(typeof AccountAPI.closeSubAccount).toBe('function')
})
})
// ==================== Transaction API 测试 ====================
describe('TransactionAPI', () => {
it('should export TransactionAPI class', async () => {
const { TransactionAPI } = await import('../transaction')
expect(TransactionAPI).toBeDefined()
})
it('should have all required methods', async () => {
const { TransactionAPI } = await import('../transaction')
// 5 个核心交易 API 方法
expect(typeof TransactionAPI.transfer).toBe('function')
expect(typeof TransactionAPI.deposit).toBe('function')
expect(typeof TransactionAPI.withdraw).toBe('function')
expect(typeof TransactionAPI.getTransaction).toBe('function')
expect(typeof TransactionAPI.getTransactions).toBe('function')
})
it('should have DepositRequest and WithdrawRequest types', async () => {
// 类型检查 - 如果编译通过则测试通过
const { TransactionAPI } = await import('../transaction')
expect(TransactionAPI.deposit).toBeDefined()
expect(TransactionAPI.withdraw).toBeDefined()
})
})
// ==================== Ledger API 测试 ====================
describe('LedgerAPI', () => {
it('should export LedgerAPI class', async () => {
const { LedgerAPI } = await import('../ledger')
expect(LedgerAPI).toBeDefined()
})
it('should have all required methods', async () => {
const { LedgerAPI } = await import('../ledger')
// 3 个账务 API 方法
expect(typeof LedgerAPI.getSubjects).toBe('function')
expect(typeof LedgerAPI.getEntry).toBe('function')
expect(typeof LedgerAPI.getAccountEntries).toBe('function')
})
})
// ==================== Reconciliation API 测试 ====================
describe('ReconciliationAPI', () => {
it('should export ReconciliationAPI class', async () => {
const { ReconciliationAPI } = await import('../reconciliation')
expect(ReconciliationAPI).toBeDefined()
})
it('should have all required methods', async () => {
const { ReconciliationAPI } = await import('../reconciliation')
// 8 个对账 API 方法
expect(typeof ReconciliationAPI.runReconciliation).toBe('function')
expect(typeof ReconciliationAPI.getBatch).toBe('function')
expect(typeof ReconciliationAPI.getBatchItems).toBe('function')
expect(typeof ReconciliationAPI.verifyThreeAccounts).toBe('function')
expect(typeof ReconciliationAPI.createAdjustment).toBe('function')
expect(typeof ReconciliationAPI.approveAdjustment).toBe('function')
expect(typeof ReconciliationAPI.rejectAdjustment).toBe('function')
expect(typeof ReconciliationAPI.getPendingAdjustments).toBe('function')
})
it('should export status label mappings', async () => {
const {
ReconciliationStatusLabels,
ReconciliationItemStatusLabels,
AdjustmentTypeLabels,
AdjustmentStatusLabels
} = await import('../reconciliation')
expect(ReconciliationStatusLabels).toBeDefined()
expect(ReconciliationItemStatusLabels).toBeDefined()
expect(AdjustmentTypeLabels).toBeDefined()
expect(AdjustmentStatusLabels).toBeDefined()
})
})
// ==================== Points API 测试 ====================
describe('PointsAPI', () => {
it('should export PointsAPI class', async () => {
const { PointsAPI } = await import('../points')
expect(PointsAPI).toBeDefined()
})
it('should have all required methods', async () => {
const { PointsAPI } = await import('../points')
// 5 个积分 API 方法
expect(typeof PointsAPI.getAccounts).toBe('function')
expect(typeof PointsAPI.earnPoints).toBe('function')
expect(typeof PointsAPI.spendPoints).toBe('function')
expect(typeof PointsAPI.transferPoints).toBe('function')
expect(typeof PointsAPI.getTransactions).toBe('function')
})
})
// ==================== Types 测试 ====================
describe('Type Definitions', () => {
it('should export points types', async () => {
const types = await import('@/types/points')
expect(types.PointsTypeLabels).toBeDefined()
expect(types.PointsTransactionTypeLabels).toBeDefined()
})
it('should export ledger types', async () => {
const types = await import('@/types/ledger')
expect(types.SubjectCategoryLabels).toBeDefined()
expect(types.DirectionLabels).toBeDefined()
expect(types.EntryStatusLabels).toBeDefined()
expect(types.PredefinedSubjects).toBeDefined()
})
})
// ==================== API Index 导出测试 ====================
describe('API Index Exports', () => {
it('should export all API modules from index', async () => {
const api = await import('../index')
expect(api.AccountAPI).toBeDefined()
expect(api.TransactionAPI).toBeDefined()
expect(api.ReconciliationAPI).toBeDefined()
expect(api.PointsAPI).toBeDefined()
expect(api.LedgerAPI).toBeDefined()
expect(api.apiClient).toBeDefined()
})
})

View File

@ -2,7 +2,7 @@
* API * API
* *
*/ */
import apiClient from './client' import { apiClient } from './client'
import type { import type {
AccountingSubject, AccountingSubject,
LedgerEntry, LedgerEntry,

View File

@ -2,7 +2,7 @@
* API * API
* // * //
*/ */
import apiClient from './client' import { apiClient } from './client'
import type { import type {
PointsAccount, PointsAccount,
PointsTransaction, PointsTransaction,

24
vitest.config.ts Normal file
View File

@ -0,0 +1,24 @@
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
plugins: [vue()],
test: {
globals: true,
environment: 'happy-dom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
include: ['src/**/*.ts'],
exclude: ['src/**/*.d.ts', 'src/**/*.test.ts', 'src/mocks/**'],
},
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})