2026-06-24 22:44:14 +08:00
|
|
|
|
// test-all.js —— 全量接口测试:权限隔离 + 数据范围隔离 + CRUD
|
|
|
|
|
|
// 用法: 先启动服务 node server.js,再运行 node test-all.js
|
2026-06-22 20:48:29 +08:00
|
|
|
|
require('dotenv').config()
|
|
|
|
|
|
const http = require('http')
|
|
|
|
|
|
|
|
|
|
|
|
const BASE = 'http://127.0.0.1:3000'
|
|
|
|
|
|
let failCount = 0
|
|
|
|
|
|
let passCount = 0
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// 所有用户的 token
|
|
|
|
|
|
const tokens = {}
|
|
|
|
|
|
|
|
|
|
|
|
// 测试中创建的数据 ID,用于最后清理
|
|
|
|
|
|
const created = {
|
|
|
|
|
|
customers: [],
|
|
|
|
|
|
employees: [],
|
|
|
|
|
|
products: [],
|
|
|
|
|
|
contracts: [],
|
|
|
|
|
|
afterSales: [],
|
|
|
|
|
|
suppliers: [],
|
|
|
|
|
|
users: [],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-22 20:48:29 +08:00
|
|
|
|
// ========== 工具函数 ==========
|
2026-06-24 22:44:14 +08:00
|
|
|
|
function req(method, path, body, token) {
|
2026-06-22 20:48:29 +08:00
|
|
|
|
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' },
|
|
|
|
|
|
}
|
2026-06-24 22:44:14 +08:00
|
|
|
|
if (token) opts.headers['Authorization'] = 'Bearer ' + token
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
|
|
|
|
|
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) }) }
|
2026-06-24 22:44:14 +08:00
|
|
|
|
catch { resolve({ status: res.statusCode, body: d.substring(0, 500) }) }
|
2026-06-22 20:48:29 +08:00
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
r.on('error', reject)
|
|
|
|
|
|
if (body) r.write(JSON.stringify(body))
|
|
|
|
|
|
r.end()
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
function check(label, res, expectStatus) {
|
|
|
|
|
|
if (res.status === expectStatus) {
|
2026-06-22 20:48:29 +08:00
|
|
|
|
passCount++
|
|
|
|
|
|
console.log(` ✅ ${label}`)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
failCount++
|
|
|
|
|
|
console.log(` ❌ ${label} 预期 HTTP ${expectStatus}, 实际 HTTP ${res.status}`)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
const preview = JSON.stringify(res.body).substring(0, 300)
|
|
|
|
|
|
if (preview) console.log(` 响应: ${preview}`)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
return res
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function checkValue(label, actual, expected) {
|
|
|
|
|
|
if (actual === expected) {
|
|
|
|
|
|
passCount++
|
|
|
|
|
|
console.log(` ✅ ${label} (${actual})`)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
failCount++
|
|
|
|
|
|
console.log(` ❌ ${label} 预期 ${expected}, 实际 ${actual}`)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 主流程 ==========
|
|
|
|
|
|
async function main() {
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('╔══════════════════════════════════════════════════╗')
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('║ 蜜雪冰城企业管理系统 — 全量接口测试 V2 ║')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('╚══════════════════════════════════════════════════╝')
|
2026-06-22 20:48:29 +08:00
|
|
|
|
console.log(`服务地址: ${BASE}\n`)
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
// 【1】 多角色登录
|
|
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
console.log('【1】 多角色登录')
|
|
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
|
|
|
|
|
|
const accounts = [
|
2026-06-26 09:44:54 +08:00
|
|
|
|
{ username: 'admin', password: '123456', label: '信息技术部' },
|
|
|
|
|
|
{ username: 'zhangchao', password: '123456', label: '总经理办公室' },
|
|
|
|
|
|
{ username: 'liming', password: '123456', label: '招商部' },
|
|
|
|
|
|
{ username: 'wangli', password: '123456', label: '运营部' },
|
|
|
|
|
|
{ username: 'zhaoqiang', password: '123456', label: '采购部' },
|
|
|
|
|
|
{ username: 'chenfang', password: '123456', label: '财务部' },
|
2026-06-24 22:44:14 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
let r = await req('POST', '/api/user/login', { username: 'admin', password: 'wrong' })
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('POST /api/user/login (错误密码 → 400)', r, 400)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
r = await req('POST', '/api/user/login', { username: '', password: '' })
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('POST /api/user/login (空参数 → 400)', r, 400)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 【2】 JWT payload 验证(新字段名)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('【2】 JWT payload 验证(departmentName / departmentDesc / permissions)')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
|
|
|
|
|
|
const adminLogin = await req('POST', '/api/user/login', { username: 'admin', password: '123456' })
|
|
|
|
|
|
const adminInfo = adminLogin.body?.data?.userInfo
|
|
|
|
|
|
if (adminInfo) {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
checkValue('admin name', adminInfo.name, '系统管理员')
|
|
|
|
|
|
checkValue('admin departmentName', adminInfo.departmentName, 'admin')
|
|
|
|
|
|
checkValue('admin departmentDesc', adminInfo.departmentDesc, '信息技术部')
|
|
|
|
|
|
check('admin permissions 是数组', { status: Array.isArray(adminInfo.permissions) ? 200 : 500 }, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
checkValue('admin permissions 数量', adminInfo.permissions?.length, 25)
|
|
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
const limingLogin = await req('POST', '/api/user/login', { username: 'liming', password: '123456' })
|
|
|
|
|
|
const limingInfo = limingLogin.body?.data?.userInfo
|
|
|
|
|
|
if (limingInfo) {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
checkValue('liming name', limingInfo.name, '李明')
|
|
|
|
|
|
checkValue('liming departmentName', limingInfo.departmentName, 'franchise_manager')
|
|
|
|
|
|
checkValue('liming departmentDesc', limingInfo.departmentDesc, '招商部')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
check('liming 有 customer:read', { status: limingInfo.permissions?.includes('customer:read') ? 200 : 500 }, 200)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('liming 有 customer:create', { status: limingInfo.permissions?.includes('customer:create') ? 200 : 500 }, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
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)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('liming 无 after_sale:read', { status: !limingInfo.permissions?.includes('after_sale:read') ? 200 : 500 }, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
const zhaoqiangLogin = await req('POST', '/api/user/login', { username: 'zhaoqiang', password: '123456' })
|
|
|
|
|
|
const zhaoqiangInfo = zhaoqiangLogin.body?.data?.userInfo
|
|
|
|
|
|
if (zhaoqiangInfo) {
|
|
|
|
|
|
checkValue('zhaoqiang name', zhaoqiangInfo.name, '赵强')
|
|
|
|
|
|
check('zhaoqiang 有 customer:read(新增权限)', { status: zhaoqiangInfo.permissions?.includes('customer:read') ? 200 : 500 }, 200)
|
|
|
|
|
|
check('zhaoqiang 有 supplier:create', { status: zhaoqiangInfo.permissions?.includes('supplier:create') ? 200 : 500 }, 200)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GET /api/user/info 返回新字段
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/user/info', null, tokens.admin)
|
|
|
|
|
|
check('GET /api/user/info (admin)', r, 200)
|
|
|
|
|
|
if (r.body?.data) {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('info 有 dept_name', { status: r.body.data.dept_name ? 200 : 500 }, 200)
|
|
|
|
|
|
check('info 有 dept_desc', { status: r.body.data.dept_desc ? 200 : 500 }, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
// 【3】 权限隔离测试
|
|
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
|
|
|
|
|
console.log('【3】 权限隔离测试(无权限应返回 403)')
|
|
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 招商部 → products(无权限)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/products', null, tokens.liming)
|
|
|
|
|
|
check('liming → GET /api/products → 403', r, 403)
|
|
|
|
|
|
r = await req('POST', '/api/products', { name: 'x' }, tokens.liming)
|
|
|
|
|
|
check('liming → POST /api/products → 403', r, 403)
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 招商部 → suppliers(无权限)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/suppliers', null, tokens.liming)
|
|
|
|
|
|
check('liming → GET /api/suppliers → 403', r, 403)
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 招商部 → after-sales(无权限)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/after-sales', null, tokens.liming)
|
|
|
|
|
|
check('liming → GET /api/after-sales → 403', r, 403)
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 运营部 → products(无权限)
|
|
|
|
|
|
r = await req('GET', '/api/products', null, tokens.wangli)
|
|
|
|
|
|
check('wangli → GET /api/products → 403', r, 403)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 运营部 → suppliers(无权限)
|
|
|
|
|
|
r = await req('GET', '/api/suppliers', null, tokens.wangli)
|
|
|
|
|
|
check('wangli → GET /api/suppliers → 403', r, 403)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 运营部 → contracts(无权限)
|
|
|
|
|
|
r = await req('GET', '/api/contracts', null, tokens.wangli)
|
|
|
|
|
|
check('wangli → GET /api/contracts → 403 (运营部无合同权限)', r, 403)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 采购部 → after-sales(无权限)
|
|
|
|
|
|
r = await req('GET', '/api/after-sales', null, tokens.zhaoqiang)
|
|
|
|
|
|
check('zhaoqiang → GET /api/after-sales → 403', r, 403)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 财务部 → POST customers(只有 read)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/customers', { name: '财务创建' }, tokens.chenfang)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('chenfang → POST /api/customers → 403 (仅可读)', r, 403)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 财务部 → POST employees(只有 read)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/employees', { name: '财务创建' }, tokens.chenfang)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('chenfang → POST /api/employees → 403 (仅可读)', r, 403)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 总经理 → POST customers(只有 read)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/customers', { name: '总经理创建' }, tokens.zhangchao)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('zhangchao → POST /api/customers → 403 (仅可读)', r, 403)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 总经理 → POST contracts(只有 read)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/contracts', { contract_name: 'x', customer_id: 1 }, tokens.zhangchao)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('zhangchao → POST /api/contracts → 403 (仅可读)', r, 403)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
// 非管理员 → 用户管理
|
|
|
|
|
|
r = await req('GET', '/api/users', null, tokens.liming)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('liming → GET /api/users → 403 (非管理员)', r, 403)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
// 无 token
|
|
|
|
|
|
r = await req('GET', '/api/customers', null, null)
|
|
|
|
|
|
check('无 token → GET /api/customers → 401', r, 401)
|
|
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 【4】 简易列表接口
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('【4】 简易列表接口(下拉选择用)')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
r = await req('GET', '/api/user/list', null, tokens.admin)
|
|
|
|
|
|
check('GET /api/user/list (admin)', r, 200)
|
|
|
|
|
|
if (r.body?.data) {
|
|
|
|
|
|
checkValue('user/list 数量 >= 6', r.body.data.length >= 6 ? 'YES' : 'NO', 'YES')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 放宽为 auth 后,非管理员也能调
|
|
|
|
|
|
r = await req('GET', '/api/user/list', null, tokens.liming)
|
|
|
|
|
|
check('GET /api/user/list (liming auth即可)', r, 200)
|
|
|
|
|
|
|
|
|
|
|
|
r = await req('GET', '/api/employees/simple', null, tokens.liming)
|
|
|
|
|
|
check('GET /api/employees/simple (liming)', r, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
r = await req('GET', '/api/customers/simple', null, tokens.liming)
|
|
|
|
|
|
check('GET /api/customers/simple (liming)', r, 200)
|
|
|
|
|
|
if (r.body?.data) {
|
|
|
|
|
|
check('customers/simple 有 id+name', { status: r.body.data[0]?.id && r.body.data[0]?.name ? 200 : 500 }, 200)
|
|
|
|
|
|
}
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
r = await req('GET', '/api/suppliers/simple', null, tokens.zhaoqiang)
|
|
|
|
|
|
check('GET /api/suppliers/simple (zhaoqiang)', r, 200)
|
|
|
|
|
|
|
|
|
|
|
|
// 无 supplier:read 的看不到供应商简易列表
|
|
|
|
|
|
r = await req('GET', '/api/suppliers/simple', null, tokens.liming)
|
|
|
|
|
|
check('GET /api/suppliers/simple (liming 无权限 → 403)', r, 403)
|
|
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
// 【5】 数据范围隔离测试(过渡期规则)
|
|
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
|
|
|
|
|
console.log('【5】 数据范围隔离测试')
|
|
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
|
|
|
|
|
|
// 5.1 加盟商:招商部看全量(过渡期)
|
|
|
|
|
|
r = await req('GET', '/api/customers', null, tokens.admin)
|
|
|
|
|
|
check('admin GET /api/customers (全量)', r, 200)
|
|
|
|
|
|
const adminCustCount = r.body?.data?.total || 0
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
r = await req('GET', '/api/customers', null, tokens.liming)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('liming GET /api/customers (全量-过渡期)', r, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
if (r.body?.data) {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
checkValue('liming 看到加盟商数 = admin', r.body.data.total >= adminCustCount ? 'YES' : 'NO', 'YES')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/customers', null, tokens.wangli)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('wangli GET /api/customers (全量-过渡期)', r, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
if (r.body?.data) {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
checkValue('wangli 看到加盟商数 = admin', r.body.data.total >= adminCustCount ? 'YES' : 'NO', 'YES')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 采购部现在也能看加盟商
|
|
|
|
|
|
r = await req('GET', '/api/customers', null, tokens.zhaoqiang)
|
|
|
|
|
|
check('zhaoqiang GET /api/customers (采购部可看)', r, 200)
|
|
|
|
|
|
|
|
|
|
|
|
// 5.2 合同类型隔离
|
|
|
|
|
|
// 全量看
|
|
|
|
|
|
r = await req('GET', '/api/contracts', null, tokens.admin)
|
|
|
|
|
|
check('admin GET /api/contracts (全量)', r, 200)
|
|
|
|
|
|
|
|
|
|
|
|
// 招商部只看 franchise
|
|
|
|
|
|
r = await req('GET', '/api/contracts', null, tokens.liming)
|
|
|
|
|
|
check('liming GET /api/contracts (只看加盟合同)', r, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
if (r.body?.data) {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
const allFranchise = (r.body.data.list || []).every(c => c.type === 'franchise')
|
|
|
|
|
|
checkValue('liming 合同全是 franchise', allFranchise ? 'YES' : 'NO', 'YES')
|
|
|
|
|
|
check('liming 有加盟合同', { status: r.body.data.total > 0 ? 200 : 500 }, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 采购部只看 supply
|
|
|
|
|
|
r = await req('GET', '/api/contracts', null, tokens.zhaoqiang)
|
|
|
|
|
|
check('zhaoqiang GET /api/contracts (只看采购合同)', r, 200)
|
|
|
|
|
|
if (r.body?.data) {
|
|
|
|
|
|
const allSupply = (r.body.data.list || []).every(c => c.type === 'supply')
|
|
|
|
|
|
checkValue('zhaoqiang 合同全是 supply', allSupply ? 'YES' : 'NO', 'YES')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 运营部无合同权限(走不到 dataScope)
|
|
|
|
|
|
r = await req('GET', '/api/contracts', null, tokens.wangli)
|
|
|
|
|
|
check('wangli → GET /api/contracts → 403', r, 403)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 5.3 售后:运营部看全量(过渡期)
|
|
|
|
|
|
r = await req('GET', '/api/after-sales', null, tokens.wangli)
|
|
|
|
|
|
check('wangli GET /api/after-sales (全量-过渡期)', r, 200)
|
|
|
|
|
|
if (r.body?.data) {
|
|
|
|
|
|
check('wangli 看到售后数据', { status: r.body.data.total > 0 ? 200 : 500 }, 200)
|
|
|
|
|
|
}
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
r = await req('GET', '/api/after-sales', null, tokens.chenfang)
|
|
|
|
|
|
check('chenfang GET /api/after-sales (财务全量)', r, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 5.4 员工部门隔离
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/employees', null, tokens.liming)
|
|
|
|
|
|
check('liming GET /api/employees (部门隔离)', r, 200)
|
|
|
|
|
|
if (r.body?.data) {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
const allInDept = (r.body.data.list || []).every(e => e.department === '招商部')
|
|
|
|
|
|
checkValue('liming 只看招商部员工', allInDept ? 'YES' : 'NO', 'YES')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/employees', null, tokens.wangli)
|
|
|
|
|
|
check('wangli GET /api/employees (部门隔离)', r, 200)
|
|
|
|
|
|
if (r.body?.data) {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
const allInDept = (r.body.data.list || []).every(e => e.department === '运营部')
|
|
|
|
|
|
checkValue('wangli 只看运营部员工', allInDept ? 'YES' : 'NO', 'YES')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/employees', null, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('admin GET /api/employees (全量)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
// 【6】 合同类型校验中间件
|
|
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
|
|
|
|
|
console.log('【6】 validateContractType 中间件测试')
|
|
|
|
|
|
console.log('━'.repeat(55))
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 获取一个加盟商 ID 用于测试
|
|
|
|
|
|
const custList = await req('GET', '/api/customers?pageSize=1', null, tokens.admin)
|
|
|
|
|
|
const testCustId = custList.body?.data?.list?.[0]?.id
|
|
|
|
|
|
|
|
|
|
|
|
// 招商部不能创建采购合同
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/contracts', {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
customer_id: testCustId, contract_name: '越权采购合同',
|
|
|
|
|
|
type: 'supply', effective_date: '2026-01-01', expiry_date: '2027-01-01',
|
|
|
|
|
|
}, tokens.liming)
|
|
|
|
|
|
check('liming → POST supply合同 → 403', r, 403)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 采购部不能创建加盟合同
|
|
|
|
|
|
r = await req('POST', '/api/contracts', {
|
|
|
|
|
|
customer_id: testCustId, contract_name: '越权加盟合同',
|
|
|
|
|
|
type: 'franchise', effective_date: '2026-01-01', expiry_date: '2027-01-01',
|
|
|
|
|
|
}, tokens.zhaoqiang)
|
|
|
|
|
|
check('zhaoqiang → POST franchise合同 → 403', r, 403)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// admin 可以创建任意类型
|
|
|
|
|
|
r = await req('POST', '/api/contracts', {
|
|
|
|
|
|
customer_id: testCustId, contract_name: 'admin测试合同',
|
|
|
|
|
|
amount: 100000, effective_date: '2026-01-01', expiry_date: '2027-01-01',
|
|
|
|
|
|
type: 'franchise', status: '生效',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('admin → POST franchise合同 → 200', r, 200)
|
|
|
|
|
|
if (r.body?.data?.id) created.contracts.push(r.body.data.id)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 【7】 suppliers CRUD
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('【7】 suppliers CRUD(采购部操作)')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
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)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('zhaoqiang POST /api/suppliers (缺名 → 400)', r, 400)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
if (supId) {
|
|
|
|
|
|
r = await req('GET', '/api/suppliers/' + supId, null, tokens.zhaoqiang)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('GET /api/suppliers/:id (详情)', r, 200)
|
|
|
|
|
|
if (r.body?.data) checkValue('供应商名称', r.body.data.name, '柠檬供应商-测试')
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
r = await req('PUT', '/api/suppliers/' + supId, { phone: '13800008888' }, tokens.zhaoqiang)
|
|
|
|
|
|
check('PUT /api/suppliers/:id (更新)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/suppliers?name=柠檬', null, tokens.zhaoqiang)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('GET /api/suppliers?name=柠檬 (搜索)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 【8】 users CRUD(新字段 department_id)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('【8】 users CRUD(管理员操作,新字段 department_id)')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
|
|
|
|
|
|
r = await req('GET', '/api/users', null, tokens.admin)
|
|
|
|
|
|
check('admin GET /api/users (列表)', r, 200)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
if (r.body?.data) {
|
|
|
|
|
|
console.log(` ↳ 用户总数: ${r.body.data.total}`)
|
|
|
|
|
|
// 验证新字段
|
|
|
|
|
|
const sample = r.body.data.list[0]
|
|
|
|
|
|
check('用户有 dept_name', { status: sample?.dept_name ? 200 : 500 }, 200)
|
|
|
|
|
|
check('用户有 dept_desc', { status: sample?.dept_desc ? 200 : 500 }, 200)
|
|
|
|
|
|
check('用户有 real_name (employees联查)', { status: sample?.real_name ? 200 : 500 }, 200)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 管理员查自己 ID
|
|
|
|
|
|
const allUsers = await req('GET', '/api/users', null, tokens.admin)
|
|
|
|
|
|
const adminUser = allUsers.body?.data?.list?.find(u => u.username === 'admin')
|
|
|
|
|
|
const adminUserId = adminUser?.id
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 创建新用户(用新字段 department_id)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/users', {
|
|
|
|
|
|
username: 'testuser_' + Date.now(),
|
|
|
|
|
|
password: 'pass123456',
|
2026-06-26 09:44:54 +08:00
|
|
|
|
department_id: 3, // franchise_manager 的 ID
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('admin POST /api/users (创建,新字段)', r, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
const newUserId = r.body?.data?.id
|
|
|
|
|
|
if (newUserId) {
|
|
|
|
|
|
created.users.push(newUserId)
|
|
|
|
|
|
if (r.body?.data) {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('新用户有 dept_name', { status: r.body.data.dept_name ? 200 : 500 }, 200)
|
|
|
|
|
|
check('新用户有 dept_desc', { status: r.body.data.dept_desc ? 200 : 500 }, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/users/' + newUserId, null, tokens.admin)
|
|
|
|
|
|
check('admin GET /api/users/:id (详情)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('PUT', '/api/users/' + newUserId, { is_active: 0 }, tokens.admin)
|
|
|
|
|
|
check('admin PUT /api/users/:id (禁用)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('PUT', '/api/users/' + newUserId, { is_active: 1, password: 'newpass123' }, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('admin PUT /api/users/:id (启用+改密)', r, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// admin 不能删除自己
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('DELETE', '/api/users/' + adminUserId, null, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('admin DELETE 自己 → 400', r, 400)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
// admin 不能禁用自己
|
|
|
|
|
|
r = await req('PUT', '/api/users/' + adminUserId, { is_active: 0 }, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('admin PUT 禁用自己 → 400', r, 400)
|
|
|
|
|
|
|
|
|
|
|
|
// admin 不能改自己部门
|
|
|
|
|
|
r = await req('PUT', '/api/users/' + adminUserId, { department_id: 3 }, tokens.admin)
|
|
|
|
|
|
check('admin PUT 改自己部门 → 400', r, 400)
|
|
|
|
|
|
|
|
|
|
|
|
// 按部门筛选用户
|
|
|
|
|
|
r = await req('GET', '/api/users?department_id=1', null, tokens.admin)
|
|
|
|
|
|
check('GET /api/users?department_id=1 (筛选admin部门)', r, 200)
|
|
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
// 【9】 allowUserCreation:创建员工同时建用户
|
|
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
|
|
|
|
|
console.log('【9】 allowUserCreation 中间件测试')
|
|
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
|
|
|
|
|
|
// 管理员创建员工同时建账号
|
|
|
|
|
|
r = await req('POST', '/api/employees', {
|
|
|
|
|
|
name: '测试员工-带账号', gender: '男', department: '招商部',
|
|
|
|
|
|
position: '专员', status: 1,
|
|
|
|
|
|
username: 'testemp_' + Date.now(),
|
|
|
|
|
|
password: 'pass123456',
|
|
|
|
|
|
department_id: 3, // franchise_manager
|
|
|
|
|
|
}, tokens.admin)
|
|
|
|
|
|
check('admin POST /api/employees (同步建用户)', r, 200)
|
|
|
|
|
|
if (r.body?.data?.id) created.employees.push(r.body.data.id)
|
|
|
|
|
|
|
|
|
|
|
|
// 非管理员创建员工不能带用户字段
|
|
|
|
|
|
r = await req('POST', '/api/employees', {
|
|
|
|
|
|
name: '测试员工-越权', department: '招商部',
|
|
|
|
|
|
username: 'hackuser', password: 'pass123', department_id: 3,
|
|
|
|
|
|
}, tokens.liming)
|
|
|
|
|
|
check('liming POST /api/employees 带 user 字段 → 403', r, 403)
|
|
|
|
|
|
|
|
|
|
|
|
// 管理员不带用户字段 → 只创建员工
|
|
|
|
|
|
r = await req('POST', '/api/employees', {
|
|
|
|
|
|
name: '测试员工-纯员工', department: '招商部', position: '专员', status: 1,
|
|
|
|
|
|
}, tokens.admin)
|
|
|
|
|
|
check('admin POST /api/employees (不建用户)', r, 200)
|
|
|
|
|
|
if (r.body?.data?.id) created.employees.push(r.body.data.id)
|
|
|
|
|
|
|
|
|
|
|
|
// 管理员建用户但缺字段
|
|
|
|
|
|
r = await req('POST', '/api/employees', {
|
|
|
|
|
|
name: '测试员工-缺字段', department: '招商部',
|
|
|
|
|
|
username: 'testuser2', // 缺 password 和 department_id
|
|
|
|
|
|
}, tokens.admin)
|
|
|
|
|
|
check('admin POST /api/employees (建用户缺字段 → 400)', r, 400)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 【10】 customers CRUD
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('【10】 customers CRUD')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
|
|
|
|
|
|
r = await req('POST', '/api/customers', {
|
|
|
|
|
|
name: 'CRUD测试加盟商', phone: '13800007777', province: '河南', city: '郑州',
|
2026-06-24 23:12:43 +08:00
|
|
|
|
email: 'test@mixue.com',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}, tokens.admin)
|
|
|
|
|
|
check('admin POST /api/customers (创建)', r, 200)
|
|
|
|
|
|
const custCrudId = r.body?.data?.id
|
|
|
|
|
|
if (custCrudId) {
|
|
|
|
|
|
created.customers.push(custCrudId)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('客户创建成功', { status: r.body.data.id ? 200 : 500 }, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/customers', { name: '' }, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('POST /api/customers (缺名 → 400)', r, 400)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
if (custCrudId) {
|
|
|
|
|
|
r = await req('GET', '/api/customers/' + custCrudId, null, tokens.admin)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
check('GET /api/customers/:id (详情)', r, 200)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
if (r.body?.data) checkValue('加盟商名', r.body.data.name, 'CRUD测试加盟商')
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
r = await req('PUT', '/api/customers/' + custCrudId, { phone: '13900006666', remark: '更新' }, tokens.admin)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
check('PUT /api/customers/:id (更新)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/customers?name=CRUD', null, tokens.admin)
|
|
|
|
|
|
check('GET /api/customers?name=CRUD (搜索)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/customers/99999', null, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('GET /api/customers/99999 (不存在 → 404)', r, 404)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 【11】 employees CRUD
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('【11】 employees CRUD')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
if (empCrudId) {
|
|
|
|
|
|
r = await req('GET', '/api/employees/' + empCrudId, null, tokens.admin)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
check('GET /api/employees/:id (详情)', r, 200)
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
r = await req('PUT', '/api/employees/' + empCrudId, { salary: 15000, position: '高级专员' }, tokens.admin)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
check('PUT /api/employees/:id (更新)', r, 200)
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/employees?department=品控部', null, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('GET /api/employees?department=品控部 (筛选)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 【12】 products CRUD
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('【12】 products CRUD(采购部操作)')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('━'.repeat(55))
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
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)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/products', { name: '' }, tokens.zhaoqiang)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('POST /api/products (缺名 → 400)', r, 400)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
|
|
|
|
|
if (prodId) {
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/products/' + prodId, null, tokens.zhaoqiang)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
check('GET /api/products/:id (详情)', r, 200)
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('PUT', '/api/products/' + prodId, { price: 6.00, quantity: 8000 }, tokens.zhaoqiang)
|
|
|
|
|
|
check('PUT /api/products/:id (更新)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/products?name=柠檬', null, tokens.zhaoqiang)
|
|
|
|
|
|
check('GET /api/products?name=柠檬 (搜索)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 【13】 contracts CRUD
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('【13】 contracts CRUD')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 招商部正确创建加盟合同
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/contracts', {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
customer_id: custCrudId, contract_name: '加盟协议-测试', amount: 300000,
|
2026-06-24 22:44:14 +08:00
|
|
|
|
effective_date: '2026-06-01', expiry_date: '2027-05-31', status: '生效',
|
2026-06-26 09:44:54 +08:00
|
|
|
|
}, tokens.liming)
|
|
|
|
|
|
check('liming POST /api/contracts (加盟合同)', r, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
if (r.body?.data) {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
checkValue('合同type自动=franchise', r.body.data.type, 'franchise')
|
|
|
|
|
|
check('合同有 customer_name 联查', { status: r.body.data.customer_name ? 200 : 500 }, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
created.contracts.push(r.body.data.id)
|
|
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 采购部正确创建采购合同(需要 supplier_id)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/contracts', {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
customer_id: custCrudId, contract_name: '采购合同-测试', amount: 200000,
|
|
|
|
|
|
effective_date: '2026-06-01', expiry_date: '2027-05-31',
|
|
|
|
|
|
type: 'supply', status: '生效', supplier_id: 1,
|
|
|
|
|
|
}, tokens.zhaoqiang)
|
|
|
|
|
|
check('zhaoqiang POST /api/contracts (采购合同)', r, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
if (r.body?.data) {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
checkValue('合同type=supply', r.body.data.type, 'supply')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
created.contracts.push(r.body.data.id)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/contracts', { customer_id: 99999, contract_name: '无效' }, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('POST /api/contracts (客户不存在 → 400)', r, 400)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 【14】 after-sales CRUD
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('【14】 after-sales CRUD')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('━'.repeat(55))
|
|
|
|
|
|
|
|
|
|
|
|
r = await req('POST', '/api/after-sales', {
|
2026-06-26 09:44:54 +08:00
|
|
|
|
customer_id: custCrudId, feedback: '冰淇淋机不出料',
|
|
|
|
|
|
handle_method: '派工程师上门', handle_status: '处理中',
|
|
|
|
|
|
service_date: '2026-06-20',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}, tokens.wangli)
|
|
|
|
|
|
check('wangli POST /api/after-sales (创建)', r, 200)
|
|
|
|
|
|
const asId = r.body?.data?.id
|
|
|
|
|
|
if (asId) {
|
|
|
|
|
|
created.afterSales.push(asId)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('售后有 customer_name', { status: r.body.data.customer_name ? 200 : 500 }, 200)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/after-sales', { customer_id: 99999, feedback: '测试' }, tokens.wangli)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('POST /api/after-sales (客户不存在 → 400)', r, 400)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
r = await req('POST', '/api/after-sales', { customer_id: custCrudId }, tokens.wangli)
|
|
|
|
|
|
check('POST /api/after-sales (缺feedback → 400)', r, 400)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
if (asId) {
|
|
|
|
|
|
r = await req('GET', '/api/after-sales/' + asId, null, tokens.wangli)
|
|
|
|
|
|
check('GET /api/after-sales/:id (详情)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('PUT', '/api/after-sales/' + asId, { handle_status: '已完成', handle_method: '更换密封圈' }, tokens.wangli)
|
|
|
|
|
|
check('PUT /api/after-sales/:id (更新)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('GET', '/api/after-sales?handle_status=已完成', null, tokens.wangli)
|
|
|
|
|
|
check('GET /api/after-sales?handle_status=已完成 (筛选)', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 【15】 个人信息 & 改密
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('【15】 个人信息 & 改密')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('━'.repeat(55))
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('POST', '/api/user/logout', null, tokens.admin)
|
|
|
|
|
|
check('POST /api/user/logout', r, 200)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('PUT', '/api/user/password', { oldPassword: 'wrong', newPassword: 'newpwd999' }, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('PUT /api/user/password (旧密码错 → 400)', r, 400)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('PUT', '/api/user/password', { oldPassword: '123456', newPassword: '123456' }, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('PUT /api/user/password (新旧相同 → 400)', r, 400)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
r = await req('PUT', '/api/user/password', { oldPassword: '123456', newPassword: '123' }, tokens.admin)
|
2026-06-26 09:44:54 +08:00
|
|
|
|
check('PUT /api/user/password (新密码太短 → 400)', r, 400)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 【16】 数据清理
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '━'.repeat(55))
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('【16】 测试数据清理')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('━'.repeat(55))
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
for (const id of created.afterSales) {
|
|
|
|
|
|
await req('DELETE', '/api/after-sales/' + id, null, tokens.admin)
|
2026-06-22 20:48:29 +08:00
|
|
|
|
}
|
2026-06-24 22:44:14 +08:00
|
|
|
|
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(' 🧹 清理完成')
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
// 收尾
|
|
|
|
|
|
// ══════════════════════════════════════════
|
|
|
|
|
|
console.log('\n' + '═'.repeat(55))
|
2026-06-22 20:48:29 +08:00
|
|
|
|
console.log(`测试完成: 通过 ${passCount} / 失败 ${failCount}`)
|
|
|
|
|
|
if (failCount > 0) {
|
|
|
|
|
|
console.log('⚠️ 存在失败用例,请检查上方输出')
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log('🎉 全部接口通过!')
|
|
|
|
|
|
}
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log('═'.repeat(55))
|
2026-06-22 20:48:29 +08:00
|
|
|
|
|
|
|
|
|
|
process.exit(failCount > 0 ? 1 : 0)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
main().catch((e) => {
|
|
|
|
|
|
console.error('测试异常:', e.message)
|
|
|
|
|
|
console.error('请确认服务已启动: node server.js')
|
|
|
|
|
|
process.exit(1)
|
|
|
|
|
|
})
|