Files
aurak/test-assessment-smoke.mjs
T
Developer 3d41f0dfcb test: 端到端全流程测试 + 烟雾测试 + 测试方案文档
新增:
1. test-e2e-assessment-full-flow.mjs — 完整端到端流程
   登录→模板校验→题库校验→API考核→非技术模板→UI端到端
   覆盖7个阶段29项检查,全部通过

2. test-assessment-smoke.mjs — 快速烟雾测试(29项)

3. docs/tests/assessment-test-plan.md — 完整测试方案文档
   5个Phase: 核心流程/评分证书/权限隔离/压力异常/回归测试

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 10:51:09 +08:00

227 lines
10 KiB
JavaScript

/**
* 烟雾测试 — 快速发现人才测评系统当前故障
*
* 覆盖 Phase 1 核心流程 + Phase 2 评分 + Phase 3 权限
* 不依赖被测系统之外的资源,纯 API + 少量 Playwright
*/
import { chromium } from 'playwright';
const API = 'http://localhost:3001';
const BASE = 'http://localhost:13001';
const TENANT_ID = 'a140a68e-f70a-44d3-b753-fa33d48cf234';
let pass = 0, fail = 0;
function ok(l, d) { pass++; console.log(`${l}${d?' — '+d:''}`); }
function no(l, d) { fail++; console.log(`${l}${d?' — '+d:''}`); }
async function login(u, p) {
const r = await fetch(`${API}/api/auth/login`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:u,password:p})});
return r.ok ? (await r.json()).access_token : null;
}
async function call(token, method, path, body=null) {
const opts = {method,headers:{Authorization:`Bearer ${token}`,'Content-Type':'application/json'}};
if(body) opts.body=JSON.stringify(body);
const r = await fetch(`${API}/api${path}`,opts);
return {status:r.status,data:await r.json().catch(()=>null)};
}
async function run() {
console.log('\n' + '█'.repeat(60));
console.log(' 🔥 烟雾测试 — 人才测评系统健康状况检查');
console.log('█'.repeat(60));
const t0 = Date.now();
// ────────── 1. 环境 ──────────
console.log('\n─── 1. 环境可达性 ───');
const adminT = await login('admin','admin123');
ok('admin 登录', !!adminT);
const taT = await login('ta_admin','pass123');
ok('ta_admin 登录', !!taT);
const u1T = await login('user1','pass123');
ok('user1 登录', !!u1T);
// ────────── 2. 模板检查 ──────────
console.log('\n─── 2. 模板与出题 ───');
// 2a. 模板列表
const tpls = await call(adminT,'GET','/assessment/templates');
ok('模板列表可获取', tpls.status === 200, `status=${tpls.status}`);
const tplArr = Array.isArray(tpls.data) ? tpls.data : [];
ok('至少有一个模板', tplArr.length > 0, `${tplArr.length}`);
const techTpl = tplArr.find(t => t.name.includes('AI协作技巧'));
const nonTechTpl = tplArr.find(t => t.name.includes('非技术人员'));
ok('技术人员模板存在', !!techTpl);
ok('非技术人员模板存在', !!nonTechTpl);
// 2b. 检查模板 attemptLimit(不要为1导致admin被锁)
if (techTpl) ok('技术人员模板 attemptLimit 正常', techTpl.attemptLimit === 0 || techTpl.attemptLimit > 1, `attemptLimit=${techTpl.attemptLimit}`);
// 2c. 题库检查
const bank = await call(adminT,'GET','/question-banks/by-template/eefe8c6c-d082-4a8c-b884-76577dde3249');
ok('题库可获取', bank.status < 300, `status=${bank.status}`);
let techBankItems = 0;
if (bank.data?.id) {
const items = await call(adminT,'GET',`/question-banks/${bank.data.id}/items`);
techBankItems = Array.isArray(items.data) ? items.data.length : (items.data?.data||[]).length;
ok('题库有题目', techBankItems > 0, `${techBankItems}`);
}
// 2d. 启动考核
const sr = await fetch(`${API}/api/assessment/start`,{method:'POST',headers:{Authorization:`Bearer ${u1T}`,'Content-Type':'application/json'},body:JSON.stringify({templateId:'eefe8c6c-d082-4a8c-b884-76577dde3249',language:'zh'})});
const sd = await sr.json();
ok('启动考核正常', sr.ok && !!sd.id, `status=${sr.status} id=${sd.id?.substring(0,8)}`);
// 2e. 异步出题等待
let questions = [];
if (sd.id) {
for (let w = 0; w < 30; w++) {
const st = await fetch(`${API}/api/assessment/${sd.id}/state`,{headers:{Authorization:`Bearer ${u1T}`}}).then(r=>r.json());
questions = st.questions || [];
if (questions.length > 0) break;
await new Promise(r => setTimeout(r, 2000));
}
ok('出题成功', questions.length > 0, `${questions.length}`);
// 2f. 维度分布检查
if (questions.length > 0) {
const dims = {};
questions.forEach(q => { dims[q.dimension] = (dims[q.dimension]||0)+1; });
ok('包含 PROMPT', (dims['PROMPT'] || 0) > 0);
ok('包含 LLM', (dims['LLM'] || 0) > 0);
// 2g. 答题
let answerOk = true;
for (let qi = 0; qi < Math.min(questions.length, 4); qi++) {
const q = questions[qi];
const isChoice = q.questionType === 'MULTIPLE_CHOICE' || q.questionType === 'TRUE_FALSE';
if (!q) continue;
await new Promise(r => setTimeout(r, 1500));
const ansR = await fetch(`${API}/api/assessment/${sd.id}/answer`,{method:'POST',headers:{Authorization:`Bearer ${u1T}`,'Content-Type':'application/json'},body:JSON.stringify({answer:isChoice?'A':'烟雾测试回答',language:'zh'})});
if (!ansR.ok) answerOk = false;
}
ok('答题提交正常', answerOk);
// 2h. 等待评分完成
await new Promise(r => setTimeout(r, 15000));
const state = await fetch(`${API}/api/assessment/${sd.id}/state`,{headers:{Authorization:`Bearer ${u1T}`}}).then(r=>r.json());
if (state.currentQuestionIndex >= state.questionCount || state.questionCount===undefined) {
ok('评分状态正常', true);
} else {
ok('评分进行中', true);
}
// 2i. 强制结束后查看证书
await fetch(`${API}/api/assessment/${sd.id}/force-end`,{method:'POST',headers:{Authorization:`Bearer ${u1T}`}});
await new Promise(r => setTimeout(r, 3000));
const cert = await fetch(`${API}/api/assessment/${sd.id}/certificate`,{headers:{Authorization:`Bearer ${u1T}`}}).then(r=>r.json());
ok('证书可获取', !!cert.id || !!cert.level, `level=${cert.level||'?'} score=${cert.totalScore||'?'}`);
ok('证书含等级', !!cert.level);
ok('证书含总分', cert.totalScore !== undefined && cert.totalScore !== null);
}
}
// ────────── 3. 权限隔离 ──────────
console.log('\n─── 3. 权限隔离 ───');
// 3a. USER 不能管理模板
const userCreateTpl = await call(u1T,'POST','/assessment/templates',{name:'x',questionCount:5});
ok('USER 创建模板被拒', userCreateTpl.status >= 400, `status=${userCreateTpl.status}`);
// 3b. TA 可创建模板
if (nonTechTpl) {
// 不用实际创建,验证 TA 能查看模板即可
const taTpls = await call(taT,'GET','/assessment/templates');
ok('TA 可查看模板', taTpls.status === 200, `status=${taTpls.status}`);
}
// 3c. 题库权限
const userBank = await call(u1T,'GET','/question-banks');
ok('USER 可查看题库', userBank.status < 400 || userBank.status === 404, `status=${userBank.status}`);
// 3d. 用户不能查看他人的答题回顾
const adminSessions = await call(adminT,'GET','/assessment/history');
const adminSessList = Array.isArray(adminSessions.data) ? adminSessions.data : [];
if (adminSessList.length > 0) {
const otherSessionId = adminSessList[0].id;
const forbiddenReview = await fetch(`${API}/api/assessment/${otherSessionId}/review`,{headers:{Authorization:`Bearer ${u1T}`}}).then(r=>r.json());
ok('USER 不能查看他人回顾', !forbiddenReview.id || forbiddenReview.statusCode >= 400, `msg=${(forbiddenReview.message||'').substring(0,30)}`);
}
// ────────── 4. 前端 UI 快速检查 ──────────
console.log('\n─── 4. 前端 UI 检查 ───');
const browser = await chromium.launch({headless:true});
const page = await browser.newPage({viewport:{width:1440,height:900}});
// Login
await page.goto(BASE+'/login',{waitUntil:'networkidle'});
await page.waitForTimeout(1000);
await page.locator('input[type="text"]').first().fill('admin');
await page.locator('input[type="password"]').first().fill('admin123');
await page.locator('button[type="submit"]').click();
await page.waitForURL('**/');
await page.waitForTimeout(1000);
// 检查考核页
await page.goto(BASE+'/assessment',{waitUntil:'networkidle'});
await page.waitForTimeout(3000);
const pageBody = await page.textContent('body');
ok('考核页渲染', pageBody.includes('AI协作') || pageBody.includes('模板'), `内容前100: ${pageBody.substring(0,100).replace(/\s+/g,' ')}`);
// 检查两个模板按钮
const techBtn = await page.locator('button:has-text("AI协作技巧-对话测评")').isVisible().catch(()=>false);
const nonTechBtn = await page.locator('button:has-text("AI协作-非技术人员测评")').isVisible().catch(()=>false);
ok('技术人员模板按钮可见', techBtn);
ok('非技术人员模板按钮可见', nonTechBtn);
// 点击开启考核
if (techBtn) {
await page.locator('button:has-text("AI协作技巧-对话测评")').first().click();
await page.waitForTimeout(500);
const startBtn = await page.locator('button:has-text("开始专业评估")').isVisible().catch(()=>false);
ok('开始评估按钮可见', startBtn);
if (startBtn) {
await page.locator('button:has-text("开始专业评估")').first().click();
await page.waitForTimeout(10000);
const hasError = await page.evaluate(() => {
const body = document.body.textContent || '';
return body.includes('Error') || body.includes('错误') || body.includes('Failed') || body.includes('找不到');
});
ok('点击开始无报错', !hasError);
const hasQuestion = await page.evaluate(() => {
const body = document.body.textContent || '';
return body.includes('问题 1') || body.includes('Question 1');
});
ok('题目已加载', hasQuestion);
}
}
await browser.close();
// ────────── 5. 结果 ──────────
const elapsed = Math.round((Date.now()-t0)/1000);
console.log('\n' + '█'.repeat(60));
console.log(` 📊 烟雾测试报告 (${elapsed}秒)`);
console.log(`${pass}${fail}`);
console.log('█'.repeat(60));
if (fail > 0) {
console.log(`\n${fail} 个测试失败,需要修复`);
process.exit(1);
} else {
console.log('\n 🎉 系统运行正常,核心流程全部通过!');
}
}
run().catch(e => { console.error('\n💥', e.message); process.exit(1); });