Files

355 lines
15 KiB
JavaScript
Raw Permalink Normal View History

2026-06-22 20:48:29 +08:00
// test-all.js —— 手动运行,逐接口验证全部 CRUD
// 用法: node test-all.js
require('dotenv').config()
const http = require('http')
const BASE = 'http://127.0.0.1:3000'
let token = ''
let failCount = 0
let passCount = 0
// ========== 工具函数 ==========
function req(method, path, body, useToken = true) {
return new Promise((resolve, reject) => {
const u = new URL(path, BASE)
const opts = {
hostname: u.hostname, port: u.port, path: u.pathname + u.search, method,
headers: { 'Content-Type': 'application/json' },
}
if (useToken && token) opts.headers['Authorization'] = 'Bearer ' + token
const r = http.request(opts, (res) => {
let d = ''
res.on('data', (c) => (d += c))
res.on('end', () => {
try { resolve({ status: res.statusCode, body: JSON.parse(d) }) }
catch { resolve({ status: res.statusCode, body: d.substring(0, 300) }) }
})
})
r.on('error', reject)
if (body) r.write(JSON.stringify(body))
r.end()
})
}
function check(label, res, expectStatus, expectInfo) {
const ok = res.status === expectStatus
if (ok) {
passCount++
console.log(`${label}`)
} else {
failCount++
console.log(`${label} 预期 HTTP ${expectStatus}, 实际 HTTP ${res.status}`)
console.log(` 响应: ${JSON.stringify(res.body).substring(0, 200)}`)
}
if (expectInfo) console.log(`${expectInfo}`)
}
// ========== 主流程 ==========
async function main() {
console.log('╔══════════════════════════════════════════╗')
console.log('║ 企业管理系统 — 接口全量测试 ║')
console.log('╚══════════════════════════════════════════╝')
console.log(`服务地址: ${BASE}\n`)
// ────────── 1. 登录 ──────────
console.log('━'.repeat(50))
console.log('【1】 用户登录模块')
console.log('━'.repeat(50))
let r = await req('POST', '/api/user/login', { username: 'admin', password: '123456' }, false)
check('POST /api/user/login (正确密码)', r, 200)
if (r.body?.data?.token) {
token = r.body.data.token
console.log(` ↳ 角色: ${r.body.data.userInfo.role}, 姓名: ${r.body.data.userInfo.real_name}`)
}
r = await req('POST', '/api/user/login', { username: 'admin', password: 'wrong' }, false)
check('POST /api/user/login (错误密码)', r, 400, '→ 账号或密码错误')
r = await req('POST', '/api/user/login', { username: '', password: '' }, false)
check('POST /api/user/login (空参数)', r, 400, '→ 用户名和密码必填')
// ────────── 2. 个人信息 ──────────
console.log('\n━'.repeat(50))
console.log('【2】 个人信息 & 改密')
console.log('━'.repeat(50))
r = await req('GET', '/api/user/info')
check('GET /api/user/info', r, 200)
if (r.body?.data) console.log(` ↳ 用户名: ${r.body.data.username}, 角色: ${r.body.data.role}`)
r = await req('POST', '/api/user/logout')
check('POST /api/user/logout', r, 200, '→ JWT 无状态,前端删 token 即可')
r = await req('PUT', '/api/user/password', { oldPassword: 'wrong', newPassword: 'newpwd999' })
check('PUT /api/user/password (旧密码错)', r, 400, '→ 旧密码错误')
r = await req('PUT', '/api/user/password', { oldPassword: '123456', newPassword: '123456' })
check('PUT /api/user/password (新旧相同)', r, 400, '→ 新密码不能与旧密码相同')
r = await req('PUT', '/api/user/password', { oldPassword: '123456', newPassword: '123' })
check('PUT /api/user/password (新密码太短)', r, 400, '→ 新密码至少 6 位')
// ────────── 3. 用户管理 CRUD ──────────
console.log('\n━'.repeat(50))
console.log('【3】 用户管理 CRUD管理员')
console.log('━'.repeat(50))
r = await req('GET', '/api/users?page=1&pageSize=10')
check('GET /api/users (列表)', r, 200)
if (r.body?.data) console.log(` ↳ 共 ${r.body.data.total} 个用户, 本页 ${r.body.data.list?.length}`)
const ts = Date.now()
const newUser = `tester_${ts}`
r = await req('POST', '/api/users', { username: newUser, password: 'pass123', real_name: '测试员', role: 'user' })
check('POST /api/users (创建)', r, 200)
let userId = r.body?.data?.id
if (userId) console.log(` ↳ 新建用户 ID: ${userId}, 用户名: ${newUser}`)
r = await req('POST', '/api/users', { username: 'admin', password: '123456' })
check('POST /api/users (重复用户名)', r, 409, '→ 用户名已存在')
r = await req('POST', '/api/users', { username: 'bad', password: '12', role: 'superman' })
check('POST /api/users (非法角色)', r, 400, '→ 角色只能为 admin 或 user')
if (userId) {
r = await req('GET', '/api/users/' + userId)
check('GET /api/users/:id (详情)', r, 200)
r = await req('PUT', '/api/users/' + userId, { real_name: '改名后的测试员', status: 1 })
check('PUT /api/users/:id (更新姓名)', r, 200)
if (r.body?.data) console.log(` ↳ 新姓名: ${r.body.data.real_name}`)
r = await req('PUT', '/api/users/' + userId, { password: 'newpass456' })
check('PUT /api/users/:id (管理员重置密码)', r, 200, '→ 管理员可直接改他人密码')
// 用新用户登录验证改密成功
const r2 = await req('POST', '/api/user/login', { username: newUser, password: 'newpass456' }, false)
check(' └ 新用户用新密码登录', r2, 200)
// 测试非管理员访问
const userToken = r2.body?.data?.token
if (userToken) {
const oldToken = token
token = userToken
r = await req('GET', '/api/users')
check(' └ 普通用户访问用户列表', r, 403, '→ 需要管理员权限')
token = oldToken
}
r = await req('DELETE', '/api/users/' + userId)
check('DELETE /api/users/:id (删除)', r, 200)
}
r = await req('DELETE', '/api/users/1')
check('DELETE /api/users/1 (删自己)', r, 400, '→ 不能删除自己')
r = await req('GET', '/api/users', null, false)
check('GET /api/users (无 token)', r, 401, '→ 未登录')
// ────────── 4. 客户管理 ──────────
console.log('\n━'.repeat(50))
console.log('【4】 客户管理 CRUD')
console.log('━'.repeat(50))
r = await req('GET', '/api/customers?page=1&pageSize=10')
check('GET /api/customers (列表)', r, 200)
if (r.body?.data) console.log(` ↳ 共 ${r.body.data.total}`)
r = await req('POST', '/api/customers', { name: '测试加盟商', phone: '13800001111', province: '广东', city: '东莞', district: '松山湖', address: '科技路88号', customer_type: 'VIP', email: 'test@test.com' })
check('POST /api/customers (创建VIP客户)', r, 200)
let custId = r.body?.data?.id
if (custId) console.log(` ↳ 新建客户 ID: ${custId}, 类型: ${r.body.data.customer_type}`)
r = await req('POST', '/api/customers', { name: '' })
check('POST /api/customers (缺姓名)', r, 400, '→ 客户姓名必填')
// 不传 customer_type 应为默认 Normal
r = await req('POST', '/api/customers', { name: '默认类型测试' })
check('POST /api/customers (不传类型默认Normal)', r, 200)
if (r.body?.data) console.log(` ↳ 默认类型: ${r.body.data.customer_type}`)
if (r.body?.data?.id) await req('DELETE', '/api/customers/' + r.body.data.id)
if (custId) {
r = await req('GET', '/api/customers/' + custId)
check('GET /api/customers/:id (详情)', r, 200)
r = await req('PUT', '/api/customers/' + custId, { phone: '13900002222', customer_type: 'Normal', remark: '更新备注' })
check('PUT /api/customers/:id (更新类型+电话)', r, 200)
if (r.body?.data) console.log(` ↳ 更新后类型: ${r.body.data.customer_type}, 电话: ${r.body.data.phone}`)
r = await req('GET', '/api/customers?name=测试')
check('GET /api/customers?name=测试 (按姓名搜索)', r, 200)
r = await req('GET', '/api/customers?customer_type=Normal')
check('GET /api/customers?customer_type=Normal (按类型筛选)', r, 200)
if (r.body?.data) console.log(` ↳ Normal 类型共 ${r.body.data.total}`)
r = await req('DELETE', '/api/customers/' + custId)
check('DELETE /api/customers/:id (删除)', r, 200)
}
r = await req('GET', '/api/customers/99999')
check('GET /api/customers/99999 (不存在)', r, 404, '→ 客户不存在')
// ────────── 5. 员工管理 ──────────
console.log('\n━'.repeat(50))
console.log('【5】 员工管理 CRUD')
console.log('━'.repeat(50))
r = await req('GET', '/api/employees?page=1&pageSize=10')
check('GET /api/employees (列表)', r, 200)
r = await req('POST', '/api/employees', { name: '测试员工', gender: '男', age: 30, education: '本科', department: '研发部', position: '工程师', salary: 15000, phone: '13700000001', email: 'emp@test.com' })
check('POST /api/employees (创建)', r, 200)
let empId = r.body?.data?.id
if (empId) console.log(` ↳ 新建员工 ID: ${empId}, 部门: ${r.body.data.department}`)
if (empId) {
r = await req('GET', '/api/employees/' + empId)
check('GET /api/employees/:id (详情)', r, 200)
r = await req('PUT', '/api/employees/' + empId, { salary: 18000, position: '高级工程师' })
check('PUT /api/employees/:id (更新)', r, 200)
r = await req('GET', '/api/employees?status=1')
check('GET /api/employees?status=1 (在职筛选)', r, 200)
r = await req('DELETE', '/api/employees/' + empId)
check('DELETE /api/employees/:id (删除)', r, 200)
}
// ────────── 6. 产品管理 ──────────
console.log('\n━'.repeat(50))
console.log('【6】 产品管理 CRUD')
console.log('━'.repeat(50))
r = await req('GET', '/api/products?page=1&pageSize=10')
check('GET /api/products (列表)', r, 200)
r = await req('POST', '/api/products', { name: '测试产品-X1', type: '电子产品', quantity: 500, price: 299.99, unit: '台', specification: '型号 X1-2026', supplier: '供应商A' })
check('POST /api/products (创建)', r, 200)
let prodId = r.body?.data?.id
if (prodId) console.log(` ↳ 新建产品 ID: ${prodId}, 库存: ${r.body.data.quantity}`)
if (prodId) {
r = await req('GET', '/api/products/' + prodId)
check('GET /api/products/:id (详情)', r, 200)
r = await req('PUT', '/api/products/' + prodId, { price: 259.99, quantity: 480 })
check('PUT /api/products/:id (更新价格库存)', r, 200)
r = await req('GET', '/api/products?name=测试')
check('GET /api/products?name=测试 (搜索)', r, 200)
r = await req('DELETE', '/api/products/' + prodId)
check('DELETE /api/products/:id (删除)', r, 200)
}
// ────────── 7. 合同管理 ──────────
console.log('\n━'.repeat(50))
console.log('【7】 合同管理 CRUD关联客户+员工)')
console.log('━'.repeat(50))
// 先创建客户和员工做外键
const cRes = await req('POST', '/api/customers', { name: '合同测试客户' })
const eRes = await req('POST', '/api/employees', { name: '合同业务员' })
const cId = cRes.body?.data?.id
const eId = eRes.body?.data?.id
console.log(` ↳ 先创建客户(${cId}) 和 员工(${eId}) 供合同关联`)
r = await req('POST', '/api/contracts', { customer_id: cId, contract_name: '年度供货协议', contract_no: 'HT-2026-088', amount: 500000, effective_date: '2026-06-01', expiry_date: '2027-05-31', employee_id: eId, status: '生效' })
check('POST /api/contracts (创建)', r, 200)
let conId = r.body?.data?.id
if (conId) {
console.log(` ↳ 新建合同 ID: ${conId}`)
if (r.body?.data?.customer_name) console.log(` ↳ 联查客户: ${r.body.data.customer_name}, 业务员: ${r.body.data.employee_name}`)
}
r = await req('POST', '/api/contracts', { customer_id: 99999, contract_name: '无效合同' })
check('POST /api/contracts (客户不存在)', r, 400, '→ 关联客户不存在')
if (conId) {
r = await req('GET', '/api/contracts')
check('GET /api/contracts (列表)', r, 200)
r = await req('GET', '/api/contracts/' + conId)
check('GET /api/contracts/:id (详情)', r, 200)
r = await req('PUT', '/api/contracts/' + conId, { status: '完成', remark: '已履约' })
check('PUT /api/contracts/:id (更新状态)', r, 200)
r = await req('GET', '/api/contracts?status=完成')
check('GET /api/contracts?status=完成 (状态筛选)', r, 200)
r = await req('DELETE', '/api/contracts/' + conId)
check('DELETE /api/contracts/:id (删除)', r, 200)
}
// 清理外键依赖数据
if (cId) await req('DELETE', '/api/customers/' + cId)
if (eId) await req('DELETE', '/api/employees/' + eId)
// ────────── 8. 售后管理 ──────────
console.log('\n━'.repeat(50))
console.log('【8】 售后管理 CRUD关联客户+员工)')
console.log('━'.repeat(50))
const c2 = await req('POST', '/api/customers', { name: '售后测试客户' })
const e2 = await req('POST', '/api/employees', { name: '售后处理员' })
const cId2 = c2.body?.data?.id
const eId2 = e2.body?.data?.id
console.log(` ↳ 先创建客户(${cId2}) 和 员工(${eId2})`)
r = await req('POST', '/api/after-sales', { customer_id: cId2, feedback: '设备运行异常,需技术支持', employee_id: eId2, handle_method: '更换故障模块', handle_status: '处理中', service_date: '2026-06-20' })
check('POST /api/after-sales (创建)', r, 200)
let asId = r.body?.data?.id
if (asId) {
console.log(` ↳ 新建售后 ID: ${asId}`)
if (r.body?.data?.customer_name) console.log(` ↳ 联查客户: ${r.body.data.customer_name}, 处理人: ${r.body.data.employee_name}`)
}
r = await req('POST', '/api/after-sales', { customer_id: 99999, feedback: '测试' })
check('POST /api/after-sales (客户不存在)', r, 400, '→ 关联客户不存在')
if (asId) {
r = await req('GET', '/api/after-sales')
check('GET /api/after-sales (列表)', r, 200)
r = await req('GET', '/api/after-sales/' + asId)
check('GET /api/after-sales/:id (详情)', r, 200)
r = await req('PUT', '/api/after-sales/' + asId, { handle_status: '已完成', handle_method: '远程升级固件解决' })
check('PUT /api/after-sales/:id (更新)', r, 200)
r = await req('GET', '/api/after-sales?handle_status=已完成')
check('GET /api/after-sales?handle_status=已完成 (状态筛选)', r, 200)
r = await req('DELETE', '/api/after-sales/' + asId)
check('DELETE /api/after-sales/:id (删除)', r, 200)
}
if (cId2) await req('DELETE', '/api/customers/' + cId2)
if (eId2) await req('DELETE', '/api/employees/' + eId2)
// ────────── 收尾 ──────────
console.log('\n' + '═'.repeat(50))
console.log(`测试完成: 通过 ${passCount} / 失败 ${failCount}`)
if (failCount > 0) {
console.log('⚠️ 存在失败用例,请检查上方输出')
} else {
console.log('🎉 全部接口通过!')
}
console.log('═'.repeat(50))
// 优雅退出
process.exit(failCount > 0 ? 1 : 0)
}
main().catch((e) => {
console.error('测试异常:', e.message)
console.error('请确认服务已启动: node server.js')
process.exit(1)
})