添加了更多功能
This commit is contained in:
781
test-all.js
781
test-all.js
@@ -1,29 +1,42 @@
|
||||
// test-all.js —— 手动运行,逐接口验证全部 CRUD
|
||||
// 用法: node test-all.js
|
||||
// test-all.js —— 全量接口测试:权限隔离 + 数据范围隔离 + CRUD
|
||||
// 用法: 先启动服务 node server.js,再运行 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
|
||||
|
||||
// 所有用户的 token
|
||||
const tokens = {}
|
||||
|
||||
// 测试中创建的数据 ID,用于最后清理
|
||||
const created = {
|
||||
customers: [],
|
||||
employees: [],
|
||||
products: [],
|
||||
contracts: [],
|
||||
afterSales: [],
|
||||
suppliers: [],
|
||||
users: [],
|
||||
}
|
||||
|
||||
// ========== 工具函数 ==========
|
||||
function req(method, path, body, useToken = true) {
|
||||
function req(method, path, body, token) {
|
||||
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
|
||||
if (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) }) }
|
||||
catch { resolve({ status: res.statusCode, body: d.substring(0, 500) }) }
|
||||
})
|
||||
})
|
||||
r.on('error', reject)
|
||||
@@ -32,318 +45,644 @@ function req(method, path, body, useToken = true) {
|
||||
})
|
||||
}
|
||||
|
||||
function check(label, res, expectStatus, expectInfo) {
|
||||
const ok = res.status === expectStatus
|
||||
if (ok) {
|
||||
function check(label, res, expectStatus) {
|
||||
if (res.status === expectStatus) {
|
||||
passCount++
|
||||
console.log(` ✅ ${label}`)
|
||||
} else {
|
||||
failCount++
|
||||
console.log(` ❌ ${label} 预期 HTTP ${expectStatus}, 实际 HTTP ${res.status}`)
|
||||
console.log(` 响应: ${JSON.stringify(res.body).substring(0, 200)}`)
|
||||
console.log(` 响应: ${JSON.stringify(res.body).substring(0, 300)}`)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function checkValue(label, actual, expected) {
|
||||
if (actual === expected) {
|
||||
passCount++
|
||||
console.log(` ✅ ${label} (${actual})`)
|
||||
} else {
|
||||
failCount++
|
||||
console.log(` ❌ ${label} 预期 ${expected}, 实际 ${actual}`)
|
||||
}
|
||||
if (expectInfo) console.log(` ↳ ${expectInfo}`)
|
||||
}
|
||||
|
||||
// ========== 主流程 ==========
|
||||
async function main() {
|
||||
console.log('╔══════════════════════════════════════════╗')
|
||||
console.log('║ 企业管理系统 — 接口全量测试 ║')
|
||||
console.log('╚══════════════════════════════════════════╝')
|
||||
console.log('╔══════════════════════════════════════════════════╗')
|
||||
console.log('║ 蜜雪冰城企业管理系统 — 全量接口测试 ║')
|
||||
console.log('╚══════════════════════════════════════════════════╝')
|
||||
console.log(`服务地址: ${BASE}\n`)
|
||||
|
||||
// ────────── 1. 登录 ──────────
|
||||
console.log('━'.repeat(50))
|
||||
console.log('【1】 用户登录模块')
|
||||
console.log('━'.repeat(50))
|
||||
// ══════════════════════════════════════════
|
||||
// 【1】 多角色登录
|
||||
// ══════════════════════════════════════════
|
||||
console.log('━'.repeat(55))
|
||||
console.log('【1】 多角色登录')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
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}`)
|
||||
const accounts = [
|
||||
{ username: 'admin', password: '123456', label: '系统管理员' },
|
||||
{ username: 'liming', password: '123456', label: '招商经理' },
|
||||
{ username: 'wangli', password: '123456', label: '运营经理' },
|
||||
{ username: 'zhaoqiang', password: '123456', label: '采购经理' },
|
||||
{ username: 'chenfang', password: '123456', label: '财务人员' },
|
||||
{ username: 'zhangchao', password: '123456', label: '总经理' },
|
||||
]
|
||||
|
||||
for (const acct of accounts) {
|
||||
const r = await req('POST', '/api/user/login', { username: acct.username, password: acct.password })
|
||||
check(`POST /api/user/login (${acct.label} ${acct.username})`, r, 200)
|
||||
if (r.body?.data?.token) {
|
||||
tokens[acct.username] = r.body.data.token
|
||||
}
|
||||
}
|
||||
|
||||
r = await req('POST', '/api/user/login', { username: 'admin', password: 'wrong' }, false)
|
||||
check('POST /api/user/login (错误密码)', r, 400, '→ 账号或密码错误')
|
||||
// 错误密码
|
||||
let r = await req('POST', '/api/user/login', { username: 'admin', password: 'wrong' })
|
||||
check('POST /api/user/login (错误密码)', r, 400)
|
||||
|
||||
r = await req('POST', '/api/user/login', { username: '', password: '' }, false)
|
||||
check('POST /api/user/login (空参数)', r, 400, '→ 用户名和密码必填')
|
||||
r = await req('POST', '/api/user/login', { username: '', password: '' })
|
||||
check('POST /api/user/login (空参数)', r, 400)
|
||||
|
||||
// ────────── 2. 个人信息 ──────────
|
||||
console.log('\n━'.repeat(50))
|
||||
console.log('【2】 个人信息 & 改密')
|
||||
console.log('━'.repeat(50))
|
||||
// ══════════════════════════════════════════
|
||||
// 【2】 JWT payload 验证
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【2】 JWT payload 验证(新字段)')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
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}`)
|
||||
// 验证 admin 登录返回的 userInfo 结构
|
||||
const adminLogin = await req('POST', '/api/user/login', { username: 'admin', password: '123456' })
|
||||
const adminInfo = adminLogin.body?.data?.userInfo
|
||||
if (adminInfo) {
|
||||
checkValue('admin userInfo.name', adminInfo.name, '系统管理员')
|
||||
checkValue('admin userInfo.roleName', adminInfo.roleName, 'admin')
|
||||
checkValue('admin userInfo.department', adminInfo.department, '信息技术部')
|
||||
check('admin userInfo.permissions 是数组', { status: Array.isArray(adminInfo.permissions) ? 200 : 500 }, 200)
|
||||
checkValue('admin permissions 数量', adminInfo.permissions?.length, 25)
|
||||
}
|
||||
|
||||
r = await req('POST', '/api/user/logout')
|
||||
check('POST /api/user/logout', r, 200, '→ JWT 无状态,前端删 token 即可')
|
||||
// 验证 liming(招商经理)的权限
|
||||
const limingLogin = await req('POST', '/api/user/login', { username: 'liming', password: '123456' })
|
||||
const limingInfo = limingLogin.body?.data?.userInfo
|
||||
if (limingInfo) {
|
||||
checkValue('liming userInfo.name', limingInfo.name, '李明')
|
||||
checkValue('liming userInfo.roleName', limingInfo.roleName, 'franchise_manager')
|
||||
checkValue('liming userInfo.department', limingInfo.department, '招商部')
|
||||
check('liming 有 customer:read', { status: limingInfo.permissions?.includes('customer:read') ? 200 : 500 }, 200)
|
||||
check('liming 无 product:read', { status: !limingInfo.permissions?.includes('product:read') ? 200 : 500 }, 200)
|
||||
check('liming 无 supplier:read', { status: !limingInfo.permissions?.includes('supplier:read') ? 200 : 500 }, 200)
|
||||
}
|
||||
|
||||
r = await req('PUT', '/api/user/password', { oldPassword: 'wrong', newPassword: 'newpwd999' })
|
||||
check('PUT /api/user/password (旧密码错)', r, 400, '→ 旧密码错误')
|
||||
// 验证 /api/user/info 返回新字段
|
||||
r = await req('GET', '/api/user/info', null, tokens.admin)
|
||||
check('GET /api/user/info (admin)', r, 200)
|
||||
if (r.body?.data) {
|
||||
check('info 包含 role_name 字段', { status: r.body.data.role_name ? 200 : 500 }, 200)
|
||||
check('info 包含 department 字段', { status: r.body.data.department ? 200 : 500 }, 200)
|
||||
}
|
||||
|
||||
r = await req('PUT', '/api/user/password', { oldPassword: '123456', newPassword: '123456' })
|
||||
check('PUT /api/user/password (新旧相同)', r, 400, '→ 新密码不能与旧密码相同')
|
||||
// ══════════════════════════════════════════
|
||||
// 【3】 权限隔离测试
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【3】 权限隔离测试(无权限应返回 403)')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
r = await req('PUT', '/api/user/password', { oldPassword: '123456', newPassword: '123' })
|
||||
check('PUT /api/user/password (新密码太短)', r, 400, '→ 新密码至少 6 位')
|
||||
// 招商经理 → products(无权限)
|
||||
r = await req('GET', '/api/products', null, tokens.liming)
|
||||
check('liming → GET /api/products → 403', r, 403)
|
||||
|
||||
// ────────── 3. 用户管理 CRUD ──────────
|
||||
console.log('\n━'.repeat(50))
|
||||
console.log('【3】 用户管理 CRUD(管理员)')
|
||||
console.log('━'.repeat(50))
|
||||
r = await req('POST', '/api/products', { name: 'x' }, tokens.liming)
|
||||
check('liming → POST /api/products → 403', r, 403)
|
||||
|
||||
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} 条`)
|
||||
// 招商经理 → suppliers(无权限)
|
||||
r = await req('GET', '/api/suppliers', null, tokens.liming)
|
||||
check('liming → GET /api/suppliers → 403', r, 403)
|
||||
|
||||
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}`)
|
||||
// 招商经理 → after-sales(无权限)
|
||||
r = await req('GET', '/api/after-sales', null, tokens.liming)
|
||||
check('liming → GET /api/after-sales → 403', r, 403)
|
||||
|
||||
r = await req('POST', '/api/users', { username: 'admin', password: '123456' })
|
||||
check('POST /api/users (重复用户名)', r, 409, '→ 用户名已存在')
|
||||
// 采购经理 → customers(无权限)
|
||||
r = await req('GET', '/api/customers', null, tokens.zhaoqiang)
|
||||
check('zhaoqiang → GET /api/customers → 403', r, 403)
|
||||
|
||||
r = await req('POST', '/api/users', { username: 'bad', password: '12', role: 'superman' })
|
||||
check('POST /api/users (非法角色)', r, 400, '→ 角色只能为 admin 或 user')
|
||||
// 采购经理 → after-sales(无权限)
|
||||
r = await req('GET', '/api/after-sales', null, tokens.zhaoqiang)
|
||||
check('zhaoqiang → GET /api/after-sales → 403', r, 403)
|
||||
|
||||
if (userId) {
|
||||
r = await req('GET', '/api/users/' + userId)
|
||||
check('GET /api/users/:id (详情)', r, 200)
|
||||
// 财务人员 → products(无权限)
|
||||
r = await req('GET', '/api/products', null, tokens.chenfang)
|
||||
check('chenfang → GET /api/products → 403', r, 403)
|
||||
|
||||
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}`)
|
||||
// 财务人员 → suppliers(无权限)
|
||||
r = await req('GET', '/api/suppliers', null, tokens.chenfang)
|
||||
check('chenfang → GET /api/suppliers → 403', r, 403)
|
||||
|
||||
r = await req('PUT', '/api/users/' + userId, { password: 'newpass456' })
|
||||
check('PUT /api/users/:id (管理员重置密码)', r, 200, '→ 管理员可直接改他人密码')
|
||||
// 财务人员 → 创建 customers(只有 read 权限,无 create)
|
||||
r = await req('POST', '/api/customers', { name: '财务创建' }, tokens.chenfang)
|
||||
check('chenfang → POST /api/customers → 403', r, 403)
|
||||
|
||||
// 用新用户登录验证改密成功
|
||||
const r2 = await req('POST', '/api/user/login', { username: newUser, password: 'newpass456' }, false)
|
||||
check(' └ 新用户用新密码登录', r2, 200)
|
||||
// 财务人员 → 创建 employees(只有 read,无 create)
|
||||
r = await req('POST', '/api/employees', { name: '财务创建' }, tokens.chenfang)
|
||||
check('chenfang → POST /api/employees → 403', r, 403)
|
||||
|
||||
// 测试非管理员访问
|
||||
const userToken = r2.body?.data?.token
|
||||
if (userToken) {
|
||||
const oldToken = token
|
||||
token = userToken
|
||||
r = await req('GET', '/api/users')
|
||||
check(' └ 普通用户访问用户列表', r, 403, '→ 需要管理员权限')
|
||||
token = oldToken
|
||||
// 总经理 → 创建 customers(只有 read,无 create)
|
||||
r = await req('POST', '/api/customers', { name: '总经理创建' }, tokens.zhangchao)
|
||||
check('zhangchao → POST /api/customers → 403', r, 403)
|
||||
|
||||
// 总经理 → 创建 contracts(只有 read,无 create)
|
||||
r = await req('POST', '/api/contracts', { contract_name: 'x', customer_id: 1 }, tokens.zhangchao)
|
||||
check('zhangchao → POST /api/contracts → 403', r, 403)
|
||||
|
||||
// 非管理员 → 用户管理
|
||||
r = await req('GET', '/api/users', null, tokens.liming)
|
||||
check('liming → GET /api/users → 403', r, 403)
|
||||
|
||||
// 无 token
|
||||
r = await req('GET', '/api/customers', null, null)
|
||||
check('无 token → GET /api/customers → 401', r, 401)
|
||||
|
||||
// ══════════════════════════════════════════
|
||||
// 【4】 数据范围隔离测试
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【4】 数据范围隔离测试')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
// 4.1 加盟商范围隔离:admin 创建 2 个加盟商,分别分配给 liming 和 wangli
|
||||
// 获取 liming 和 wangli 的 user ID
|
||||
const allUsers = await req('GET', '/api/users', null, tokens.admin)
|
||||
const limingUserId = allUsers.body?.data?.list?.find(u => u.username === 'liming')?.id
|
||||
const wangliUserId = allUsers.body?.data?.list?.find(u => u.username === 'wangli')?.id
|
||||
|
||||
r = await req('POST', '/api/customers', { name: '加盟商A-归属liming', phone: '13811111111', responsible_user_id: limingUserId }, tokens.admin)
|
||||
check('admin 创建加盟商A (归属liming)', r, 200)
|
||||
const custAId = r.body?.data?.id
|
||||
if (custAId) created.customers.push(custAId)
|
||||
|
||||
r = await req('POST', '/api/customers', { name: '加盟商B-归属wangli', phone: '13822222222', responsible_user_id: wangliUserId }, tokens.admin)
|
||||
check('admin 创建加盟商B (归属wangli)', r, 200)
|
||||
const custBId = r.body?.data?.id
|
||||
if (custBId) created.customers.push(custBId)
|
||||
|
||||
// liming 只能看到自己负责的
|
||||
r = await req('GET', '/api/customers', null, tokens.liming)
|
||||
check('liming GET /api/customers (应只看到自己的)', r, 200)
|
||||
if (r.body?.data) {
|
||||
const limingCusts = r.body.data.list || []
|
||||
const allBelongToLiming = limingCusts.every(c => c.responsible_user_id === limingUserId)
|
||||
checkValue('liming 只看到自己的加盟商', allBelongToLiming ? 'YES' : 'NO', 'YES')
|
||||
checkValue('liming 加盟商数量 >= 1', limingCusts.length >= 1 ? 'YES' : 'NO', 'YES')
|
||||
}
|
||||
|
||||
// wangli 只能看到自己负责的
|
||||
r = await req('GET', '/api/customers', null, tokens.wangli)
|
||||
check('wangli GET /api/customers (应只看到自己的)', r, 200)
|
||||
if (r.body?.data) {
|
||||
const wangliCusts = r.body.data.list || []
|
||||
const allBelongToWangli = wangliCusts.every(c => c.responsible_user_id === wangliUserId)
|
||||
checkValue('wangli 只看到自己的加盟商', allBelongToWangli ? 'YES' : 'NO', 'YES')
|
||||
}
|
||||
|
||||
// admin 能看到全部
|
||||
r = await req('GET', '/api/customers', null, tokens.admin)
|
||||
check('admin GET /api/customers (应看到全部)', r, 200)
|
||||
if (r.body?.data) {
|
||||
checkValue('admin 加盟商总数 >= 2', r.body.data.total >= 2 ? 'YES' : 'NO', 'YES')
|
||||
}
|
||||
|
||||
// liming 不能看 wangli 的加盟商详情
|
||||
if (custBId) {
|
||||
r = await req('GET', '/api/customers/' + custBId, null, tokens.liming)
|
||||
check('liming GET wangli的加盟商详情 → 404', r, 404)
|
||||
}
|
||||
|
||||
// liming 不能修改 wangli 的加盟商
|
||||
if (custBId) {
|
||||
r = await req('PUT', '/api/customers/' + custBId, { name: '越权修改' }, tokens.liming)
|
||||
check('liming PUT wangli的加盟商 → 404', r, 404)
|
||||
}
|
||||
|
||||
// 4.2 员工部门隔离:admin 创建 2 个不同部门的员工
|
||||
r = await req('POST', '/api/employees', { name: '招商部测试员工', department: '招商部', position: '专员', status: 1 }, tokens.admin)
|
||||
check('admin 创建招商部员工', r, 200)
|
||||
const empLmId = r.body?.data?.id
|
||||
if (empLmId) created.employees.push(empLmId)
|
||||
|
||||
r = await req('POST', '/api/employees', { name: '运营部测试员工', department: '运营部', position: '专员', status: 1 }, tokens.admin)
|
||||
check('admin 创建运营部员工', r, 200)
|
||||
const empWlId = r.body?.data?.id
|
||||
if (empWlId) created.employees.push(empWlId)
|
||||
|
||||
// liming 只能看到招商部员工
|
||||
r = await req('GET', '/api/employees', null, tokens.liming)
|
||||
check('liming GET /api/employees (部门隔离)', r, 200)
|
||||
if (r.body?.data) {
|
||||
const limingEmps = r.body.data.list || []
|
||||
const allInDept = limingEmps.every(e => e.department === '招商部')
|
||||
checkValue('liming 只看到招商部员工', allInDept ? 'YES' : 'NO', 'YES')
|
||||
}
|
||||
|
||||
// wangli 只能看到运营部员工
|
||||
r = await req('GET', '/api/employees', null, tokens.wangli)
|
||||
check('wangli GET /api/employees (部门隔离)', r, 200)
|
||||
if (r.body?.data) {
|
||||
const wangliEmps = r.body.data.list || []
|
||||
const allInDept = wangliEmps.every(e => e.department === '运营部')
|
||||
checkValue('wangli 只看到运营部员工', allInDept ? 'YES' : 'NO', 'YES')
|
||||
}
|
||||
|
||||
// admin 看到全部
|
||||
r = await req('GET', '/api/employees', null, tokens.admin)
|
||||
check('admin GET /api/employees (应看到全部)', r, 200)
|
||||
if (r.body?.data) {
|
||||
checkValue('admin 员工总数 >= 2', r.body.data.total >= 2 ? 'YES' : 'NO', 'YES')
|
||||
}
|
||||
|
||||
// 4.3 合同范围隔离
|
||||
// admin 创建加盟合同(归属 liming)和采购合同
|
||||
r = await req('POST', '/api/contracts', {
|
||||
customer_id: custAId, contract_name: '加盟合同A', amount: 100000,
|
||||
effective_date: '2026-01-01', expiry_date: '2027-01-01', status: '生效',
|
||||
type: 'franchise',
|
||||
}, tokens.admin)
|
||||
check('admin 创建加盟合同 (归属liming的客户)', r, 200)
|
||||
const franchiseConId = r.body?.data?.id
|
||||
if (franchiseConId) created.contracts.push(franchiseConId)
|
||||
|
||||
r = await req('POST', '/api/contracts', {
|
||||
customer_id: custAId, contract_name: '采购合同X', amount: 200000,
|
||||
effective_date: '2026-01-01', expiry_date: '2027-01-01', status: '生效',
|
||||
type: 'supply',
|
||||
}, tokens.admin)
|
||||
check('admin 创建采购合同', r, 200)
|
||||
const supplyConId = r.body?.data?.id
|
||||
if (supplyConId) created.contracts.push(supplyConId)
|
||||
|
||||
// 采购经理只看 supply 合同
|
||||
r = await req('GET', '/api/contracts', null, tokens.zhaoqiang)
|
||||
check('zhaoqiang GET /api/contracts (只看supply)', r, 200)
|
||||
if (r.body?.data) {
|
||||
const zhaoContracts = r.body.data.list || []
|
||||
const allSupply = zhaoContracts.every(c => c.type === 'supply')
|
||||
checkValue('zhaoqiang 只看到supply合同', allSupply ? 'YES' : 'NO', 'YES')
|
||||
}
|
||||
|
||||
// 4.4 after_sales 范围隔离:admin 创建售后记录
|
||||
r = await req('POST', '/api/after-sales', {
|
||||
customer_id: custAId, feedback: '设备故障,归属wangli处理',
|
||||
responsible_user_id: wangliUserId, handle_status: '待处理',
|
||||
}, tokens.admin)
|
||||
check('admin 创建售后记录 (归属wangli)', r, 200)
|
||||
const asScopeId = r.body?.data?.id
|
||||
if (asScopeId) created.afterSales.push(asScopeId)
|
||||
|
||||
r = await req('POST', '/api/after-sales', {
|
||||
customer_id: custBId, feedback: '原料质量问题,归属当前用户处理',
|
||||
handle_status: '待处理',
|
||||
}, tokens.wangli)
|
||||
check('wangli 创建售后记录 (默认归属自己)', r, 200)
|
||||
const asScopeId2 = r.body?.data?.id
|
||||
if (asScopeId2) created.afterSales.push(asScopeId2)
|
||||
|
||||
// wangli 应该能看到 2 条售后(都是自己负责的)
|
||||
r = await req('GET', '/api/after-sales', null, tokens.wangli)
|
||||
check('wangli GET /api/after-sales (应看到自己负责的)', r, 200)
|
||||
if (r.body?.data) {
|
||||
const wangliAS = r.body.data.list || []
|
||||
const allBelong = wangliAS.every(a => a.responsible_user_id === wangliUserId)
|
||||
checkValue('wangli 只看到自己的售后', allBelong ? 'YES' : 'NO', 'YES')
|
||||
}
|
||||
|
||||
// liming 没有 after_sale:read 权限,直接 403
|
||||
r = await req('GET', '/api/after-sales', null, tokens.liming)
|
||||
check('liming GET /api/after-sales → 403 (无权限)', r, 403)
|
||||
|
||||
// ══════════════════════════════════════════
|
||||
// 【5】 suppliers CRUD 测试
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【5】 suppliers CRUD(采购经理操作)')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
r = await req('POST', '/api/suppliers', {
|
||||
name: '柠檬供应商-测试', contact: '张三', phone: '13800009999',
|
||||
address: '四川安岳', type: '原材料', content: '优质柠檬供应商',
|
||||
}, tokens.zhaoqiang)
|
||||
check('zhaoqiang POST /api/suppliers (创建)', r, 200)
|
||||
const supId = r.body?.data?.id
|
||||
if (supId) created.suppliers.push(supId)
|
||||
|
||||
r = await req('POST', '/api/suppliers', { name: '' }, tokens.zhaoqiang)
|
||||
check('zhaoqiang POST /api/suppliers (缺名称)', r, 400)
|
||||
|
||||
if (supId) {
|
||||
r = await req('GET', '/api/suppliers/' + supId, null, tokens.zhaoqiang)
|
||||
check('zhaoqiang GET /api/suppliers/:id (详情)', r, 200)
|
||||
if (r.body?.data) {
|
||||
checkValue('供应商名称', r.body.data.name, '柠檬供应商-测试')
|
||||
}
|
||||
|
||||
r = await req('DELETE', '/api/users/' + userId)
|
||||
check('DELETE /api/users/:id (删除)', r, 200)
|
||||
r = await req('PUT', '/api/suppliers/' + supId, { phone: '13800008888', remark: '更新联系方式' }, tokens.zhaoqiang)
|
||||
check('zhaoqiang PUT /api/suppliers/:id (更新)', r, 200)
|
||||
|
||||
r = await req('GET', '/api/suppliers', null, tokens.zhaoqiang)
|
||||
check('zhaoqiang GET /api/suppliers (列表)', r, 200)
|
||||
if (r.body?.data) console.log(` ↳ 供应商总数: ${r.body.data.total}`)
|
||||
|
||||
r = await req('GET', '/api/suppliers?name=柠檬', null, tokens.zhaoqiang)
|
||||
check('zhaoqiang GET /api/suppliers?name=柠檬 (搜索)', r, 200)
|
||||
}
|
||||
|
||||
r = await req('DELETE', '/api/users/1')
|
||||
check('DELETE /api/users/1 (删自己)', r, 400, '→ 不能删除自己')
|
||||
// ══════════════════════════════════════════
|
||||
// 【6】 users CRUD 测试(适配新字段)
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【6】 users CRUD(管理员操作)')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
r = await req('GET', '/api/users', null, false)
|
||||
check('GET /api/users (无 token)', r, 401, '→ 未登录')
|
||||
r = await req('GET', '/api/users', null, tokens.admin)
|
||||
check('admin GET /api/users (列表)', r, 200)
|
||||
if (r.body?.data) console.log(` ↳ 用户总数: ${r.body.data.total}`)
|
||||
|
||||
// ────────── 4. 客户管理 ──────────
|
||||
console.log('\n━'.repeat(50))
|
||||
console.log('【4】 客户管理 CRUD')
|
||||
console.log('━'.repeat(50))
|
||||
// 创建新用户,关联到已有员工
|
||||
r = await req('POST', '/api/users', {
|
||||
username: 'testuser_' + Date.now(),
|
||||
password: 'pass123456',
|
||||
role_id: 3, // franchise_manager
|
||||
employee_id: empLmId,
|
||||
department: '招商部',
|
||||
}, tokens.admin)
|
||||
check('admin POST /api/users (创建带role_id+employee_id)', r, 200)
|
||||
const newUserId = r.body?.data?.id
|
||||
if (newUserId) {
|
||||
created.users.push(newUserId)
|
||||
if (r.body?.data) {
|
||||
check('新用户有 role_name', { status: r.body.data.role_name ? 200 : 500 }, 200)
|
||||
check('新用户有 real_name (从employees)', { status: r.body.data.real_name ? 200 : 500 }, 200)
|
||||
console.log(` ↳ 新用户: ${r.body.data.username}, 角色: ${r.body.data.role_name}, 姓名: ${r.body.data.real_name}`)
|
||||
}
|
||||
|
||||
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('GET', '/api/users/' + newUserId, null, tokens.admin)
|
||||
check('admin GET /api/users/:id (详情)', r, 200)
|
||||
|
||||
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('PUT', '/api/users/' + newUserId, { is_active: 0 }, tokens.admin)
|
||||
check('admin PUT /api/users/:id (禁用)', r, 200)
|
||||
if (r.body?.data) checkValue('is_active 已禁用', r.body.data.is_active, 0)
|
||||
|
||||
r = await req('POST', '/api/customers', { name: '' })
|
||||
check('POST /api/customers (缺姓名)', r, 400, '→ 客户姓名必填')
|
||||
r = await req('PUT', '/api/users/' + newUserId, { is_active: 1, password: 'newpass123' }, tokens.admin)
|
||||
check('admin PUT /api/users/:id (启用+重置密码)', r, 200)
|
||||
}
|
||||
|
||||
// 不传 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)
|
||||
// admin 不能删除自己(先获取 admin 的真实 ID)
|
||||
const adminUser = allUsers.body?.data?.list?.find(u => u.username === 'admin')
|
||||
const adminUserId = adminUser?.id
|
||||
|
||||
if (custId) {
|
||||
r = await req('GET', '/api/customers/' + custId)
|
||||
r = await req('DELETE', '/api/users/' + adminUserId, null, tokens.admin)
|
||||
check('admin DELETE /api/users/' + adminUserId + ' (删自己) → 400', r, 400)
|
||||
|
||||
// admin 不能禁用自己
|
||||
r = await req('PUT', '/api/users/' + adminUserId, { is_active: 0 }, tokens.admin)
|
||||
check('admin PUT /api/users/' + adminUserId + ' (禁用自己) → 400', r, 400)
|
||||
|
||||
// ══════════════════════════════════════════
|
||||
// 【7】 customers CRUD 测试
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【7】 customers CRUD')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
r = await req('POST', '/api/customers', {
|
||||
name: 'CRUD测试加盟商', phone: '13800007777', province: '河南', city: '郑州',
|
||||
customer_type: 'VIP', email: 'test@mixue.com',
|
||||
}, tokens.admin)
|
||||
check('admin POST /api/customers (创建)', r, 200)
|
||||
const custCrudId = r.body?.data?.id
|
||||
if (custCrudId) {
|
||||
created.customers.push(custCrudId)
|
||||
checkValue('默认 responsible_user_id = admin', r.body.data.responsible_user_id, adminUserId)
|
||||
}
|
||||
|
||||
r = await req('POST', '/api/customers', { name: '' }, tokens.admin)
|
||||
check('POST /api/customers (缺名称)', r, 400)
|
||||
|
||||
if (custCrudId) {
|
||||
r = await req('GET', '/api/customers/' + custCrudId, null, tokens.admin)
|
||||
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('PUT', '/api/customers/' + custCrudId, { phone: '13900006666', remark: '更新备注' }, tokens.admin)
|
||||
check('PUT /api/customers/:id (更新)', r, 200)
|
||||
|
||||
r = await req('GET', '/api/customers?name=测试')
|
||||
check('GET /api/customers?name=测试 (按姓名搜索)', r, 200)
|
||||
r = await req('GET', '/api/customers?name=CRUD', null, tokens.admin)
|
||||
check('GET /api/customers?name=CRUD (搜索)', 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?customer_type=VIP', null, tokens.admin)
|
||||
check('GET /api/customers?customer_type=VIP (筛选)', r, 200)
|
||||
}
|
||||
|
||||
r = await req('GET', '/api/customers/99999')
|
||||
check('GET /api/customers/99999 (不存在)', r, 404, '→ 客户不存在')
|
||||
r = await req('GET', '/api/customers/99999', null, tokens.admin)
|
||||
check('GET /api/customers/99999 (不存在)', r, 404)
|
||||
|
||||
// ────────── 5. 员工管理 ──────────
|
||||
console.log('\n━'.repeat(50))
|
||||
console.log('【5】 员工管理 CRUD')
|
||||
console.log('━'.repeat(50))
|
||||
// ══════════════════════════════════════════
|
||||
// 【8】 employees CRUD 测试
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【8】 employees CRUD')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
r = await req('GET', '/api/employees?page=1&pageSize=10')
|
||||
check('GET /api/employees (列表)', r, 200)
|
||||
r = await req('POST', '/api/employees', {
|
||||
name: 'CRUD测试员工', gender: '男', age: 28, education: '本科',
|
||||
department: '品控部', position: '品控专员', salary: 12000,
|
||||
phone: '13700005555', email: 'emp@mixue.com', status: 1,
|
||||
}, tokens.admin)
|
||||
check('admin POST /api/employees (创建)', r, 200)
|
||||
const empCrudId = r.body?.data?.id
|
||||
if (empCrudId) created.employees.push(empCrudId)
|
||||
|
||||
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)
|
||||
if (empCrudId) {
|
||||
r = await req('GET', '/api/employees/' + empCrudId, null, tokens.admin)
|
||||
check('GET /api/employees/:id (详情)', r, 200)
|
||||
|
||||
r = await req('PUT', '/api/employees/' + empId, { salary: 18000, position: '高级工程师' })
|
||||
r = await req('PUT', '/api/employees/' + empCrudId, { salary: 15000, position: '高级品控专员' }, tokens.admin)
|
||||
check('PUT /api/employees/:id (更新)', r, 200)
|
||||
|
||||
r = await req('GET', '/api/employees?status=1')
|
||||
r = await req('GET', '/api/employees?status=1', null, tokens.admin)
|
||||
check('GET /api/employees?status=1 (在职筛选)', r, 200)
|
||||
|
||||
r = await req('DELETE', '/api/employees/' + empId)
|
||||
check('DELETE /api/employees/:id (删除)', r, 200)
|
||||
r = await req('GET', '/api/employees?department=品控部', null, tokens.admin)
|
||||
check('GET /api/employees?department=品控部 (部门筛选)', r, 200)
|
||||
}
|
||||
|
||||
// ────────── 6. 产品管理 ──────────
|
||||
console.log('\n━'.repeat(50))
|
||||
console.log('【6】 产品管理 CRUD')
|
||||
console.log('━'.repeat(50))
|
||||
// ══════════════════════════════════════════
|
||||
// 【9】 products CRUD 测试
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【9】 products CRUD(采购经理操作)')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
r = await req('GET', '/api/products?page=1&pageSize=10')
|
||||
check('GET /api/products (列表)', r, 200)
|
||||
r = await req('POST', '/api/products', {
|
||||
name: '安岳柠檬-测试', type: '原材料', quantity: 10000,
|
||||
price: 5.50, unit: 'kg', specification: '特级', supplier: '安岳柠檬基地',
|
||||
}, tokens.zhaoqiang)
|
||||
check('zhaoqiang POST /api/products (创建)', r, 200)
|
||||
const prodId = r.body?.data?.id
|
||||
if (prodId) created.products.push(prodId)
|
||||
|
||||
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}`)
|
||||
r = await req('POST', '/api/products', { name: '' }, tokens.zhaoqiang)
|
||||
check('POST /api/products (缺名称)', r, 400)
|
||||
|
||||
if (prodId) {
|
||||
r = await req('GET', '/api/products/' + prodId)
|
||||
r = await req('GET', '/api/products/' + prodId, null, tokens.zhaoqiang)
|
||||
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('PUT', '/api/products/' + prodId, { price: 6.00, quantity: 8000 }, tokens.zhaoqiang)
|
||||
check('PUT /api/products/:id (更新)', r, 200)
|
||||
|
||||
r = await req('GET', '/api/products?name=测试')
|
||||
check('GET /api/products?name=测试 (搜索)', r, 200)
|
||||
r = await req('GET', '/api/products?name=柠檬', null, tokens.zhaoqiang)
|
||||
check('GET /api/products?name=柠檬 (搜索)', r, 200)
|
||||
|
||||
r = await req('DELETE', '/api/products/' + prodId)
|
||||
check('DELETE /api/products/:id (删除)', r, 200)
|
||||
r = await req('GET', '/api/products?type=原材料', null, tokens.zhaoqiang)
|
||||
check('GET /api/products?type=原材料 (筛选)', r, 200)
|
||||
}
|
||||
|
||||
// ────────── 7. 合同管理 ──────────
|
||||
console.log('\n━'.repeat(50))
|
||||
console.log('【7】 合同管理 CRUD(关联客户+员工)')
|
||||
console.log('━'.repeat(50))
|
||||
// ══════════════════════════════════════════
|
||||
// 【10】 contracts CRUD 测试
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【10】 contracts CRUD(含 type 字段)')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
// 先创建客户和员工做外键
|
||||
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}`)
|
||||
// 采购经理创建合同 → 自动 type='supply'
|
||||
r = await req('POST', '/api/contracts', {
|
||||
customer_id: custAId, contract_name: '原材料采购合同', amount: 500000,
|
||||
effective_date: '2026-06-01', expiry_date: '2027-05-31', status: '生效',
|
||||
}, tokens.zhaoqiang)
|
||||
check('zhaoqiang POST /api/contracts (自动type=supply)', r, 200)
|
||||
if (r.body?.data) {
|
||||
checkValue('合同type自动设为supply', r.body.data.type, 'supply')
|
||||
created.contracts.push(r.body.data.id)
|
||||
}
|
||||
|
||||
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)
|
||||
// 管理员创建合同 → 默认 type='franchise'
|
||||
r = await req('POST', '/api/contracts', {
|
||||
customer_id: custAId, contract_name: '加盟协议-测试', amount: 300000,
|
||||
effective_date: '2026-06-01', expiry_date: '2027-05-31', status: '生效',
|
||||
employee_id: empLmId,
|
||||
}, tokens.admin)
|
||||
check('admin POST /api/contracts (默认type=franchise)', r, 200)
|
||||
if (r.body?.data) {
|
||||
checkValue('合同type默认franchise', r.body.data.type, 'franchise')
|
||||
check('合同有 customer_name 联查', { status: r.body.data.customer_name ? 200 : 500 }, 200)
|
||||
created.contracts.push(r.body.data.id)
|
||||
}
|
||||
|
||||
// 清理外键依赖数据
|
||||
if (cId) await req('DELETE', '/api/customers/' + cId)
|
||||
if (eId) await req('DELETE', '/api/employees/' + eId)
|
||||
r = await req('POST', '/api/contracts', { customer_id: 99999, contract_name: '无效' }, tokens.admin)
|
||||
check('POST /api/contracts (客户不存在)', r, 400)
|
||||
|
||||
// ────────── 8. 售后管理 ──────────
|
||||
console.log('\n━'.repeat(50))
|
||||
console.log('【8】 售后管理 CRUD(关联客户+员工)')
|
||||
console.log('━'.repeat(50))
|
||||
r = await req('GET', '/api/contracts', null, tokens.admin)
|
||||
check('GET /api/contracts (列表)', r, 200)
|
||||
|
||||
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})`)
|
||||
// ══════════════════════════════════════════
|
||||
// 【11】 after-sales CRUD 测试
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【11】 after-sales CRUD')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
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
|
||||
r = await req('POST', '/api/after-sales', {
|
||||
customer_id: custAId, feedback: '冰淇淋机不出料',
|
||||
employee_id: empWlId, handle_method: '派工程师上门检修',
|
||||
handle_status: '处理中', service_date: '2026-06-20',
|
||||
}, tokens.wangli)
|
||||
check('wangli POST /api/after-sales (创建)', r, 200)
|
||||
const 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}`)
|
||||
created.afterSales.push(asId)
|
||||
check('售后有 customer_name 联查', { status: r.body.data.customer_name ? 200 : 500 }, 200)
|
||||
}
|
||||
|
||||
r = await req('POST', '/api/after-sales', { customer_id: 99999, feedback: '测试' })
|
||||
check('POST /api/after-sales (客户不存在)', r, 400, '→ 关联客户不存在')
|
||||
r = await req('POST', '/api/after-sales', { customer_id: 99999, feedback: '测试' }, tokens.wangli)
|
||||
check('POST /api/after-sales (客户不存在)', r, 400)
|
||||
|
||||
r = await req('POST', '/api/after-sales', { customer_id: custAId }, tokens.wangli)
|
||||
check('POST /api/after-sales (缺feedback)', 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)
|
||||
r = await req('GET', '/api/after-sales/' + asId, null, tokens.wangli)
|
||||
check('GET /api/after-sales/:id (详情)', r, 200)
|
||||
|
||||
r = await req('PUT', '/api/after-sales/' + asId, { handle_status: '已完成', handle_method: '远程升级固件解决' })
|
||||
r = await req('PUT', '/api/after-sales/' + asId, { handle_status: '已完成', handle_method: '更换密封圈' }, tokens.wangli)
|
||||
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)
|
||||
r = await req('GET', '/api/after-sales?handle_status=已完成', null, tokens.wangli)
|
||||
check('GET /api/after-sales?handle_status=已完成 (筛选)', r, 200)
|
||||
}
|
||||
|
||||
if (cId2) await req('DELETE', '/api/customers/' + cId2)
|
||||
if (eId2) await req('DELETE', '/api/employees/' + eId2)
|
||||
// ══════════════════════════════════════════
|
||||
// 【12】 个人信息 & 改密
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【12】 个人信息 & 改密')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
// ────────── 收尾 ──────────
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
r = await req('POST', '/api/user/logout', null, tokens.admin)
|
||||
check('POST /api/user/logout', r, 200)
|
||||
|
||||
r = await req('PUT', '/api/user/password', { oldPassword: 'wrong', newPassword: 'newpwd999' }, tokens.admin)
|
||||
check('PUT /api/user/password (旧密码错)', r, 400)
|
||||
|
||||
r = await req('PUT', '/api/user/password', { oldPassword: '123456', newPassword: '123456' }, tokens.admin)
|
||||
check('PUT /api/user/password (新旧相同)', r, 400)
|
||||
|
||||
r = await req('PUT', '/api/user/password', { oldPassword: '123456', newPassword: '123' }, tokens.admin)
|
||||
check('PUT /api/user/password (新密码太短)', r, 400)
|
||||
|
||||
// ══════════════════════════════════════════
|
||||
// 【13】 数据清理
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '━'.repeat(55))
|
||||
console.log('【13】 测试数据清理')
|
||||
console.log('━'.repeat(55))
|
||||
|
||||
// 按依赖顺序删除
|
||||
for (const id of created.afterSales) {
|
||||
await req('DELETE', '/api/after-sales/' + id, null, tokens.admin)
|
||||
}
|
||||
for (const id of created.contracts) {
|
||||
await req('DELETE', '/api/contracts/' + id, null, tokens.admin)
|
||||
}
|
||||
for (const id of created.customers) {
|
||||
await req('DELETE', '/api/customers/' + id, null, tokens.admin)
|
||||
}
|
||||
for (const id of created.employees) {
|
||||
await req('DELETE', '/api/employees/' + id, null, tokens.admin)
|
||||
}
|
||||
for (const id of created.products) {
|
||||
await req('DELETE', '/api/products/' + id, null, tokens.admin)
|
||||
}
|
||||
for (const id of created.suppliers) {
|
||||
await req('DELETE', '/api/suppliers/' + id, null, tokens.admin)
|
||||
}
|
||||
for (const id of created.users) {
|
||||
await req('DELETE', '/api/users/' + id, null, tokens.admin)
|
||||
}
|
||||
console.log(' 🧹 清理完成')
|
||||
|
||||
// ══════════════════════════════════════════
|
||||
// 收尾
|
||||
// ══════════════════════════════════════════
|
||||
console.log('\n' + '═'.repeat(55))
|
||||
console.log(`测试完成: 通过 ${passCount} / 失败 ${failCount}`)
|
||||
if (failCount > 0) {
|
||||
console.log('⚠️ 存在失败用例,请检查上方输出')
|
||||
} else {
|
||||
console.log('🎉 全部接口通过!')
|
||||
}
|
||||
console.log('═'.repeat(50))
|
||||
console.log('═'.repeat(55))
|
||||
|
||||
// 优雅退出
|
||||
process.exit(failCount > 0 ? 1 : 0)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user