// ============================================================ // test_bot_chess_api_smoke.js —— bot_chess HTTP 冒烟集成测试 // 运行:node dev_test_scripts/integration/test_bot_chess_api_smoke.js // // 临时起一个 express 实例,真实绑定 thought_lab 全部路由(含 bot_chess // bindPublicRoutes 公开路由),验证:鉴权穿透、建赛、state、走子、 // 非法走子错误码、轮次保护、MD 日志、删除。全程不碰 8976/8977 端口。 // ============================================================ const express = require('express') const http = require('http') const thoughtLab = require('../../src/server/thought_lab') const COOKIE = 'nav_gate=1' let passed = 0, failed = 0 const app = express() app.use(express.json()) thoughtLab.bindRoutes(app) const server = http.createServer(app) async function req(method, path, body) { const opts = { method, headers: { Cookie: COOKIE } } if (body !== undefined) { opts.headers['Content-Type'] = 'application/json' opts.body = JSON.stringify(body) } const resp = await fetch(`http://127.0.0.1:${server.address().port}${path}`, opts) let data = null try { data = await resp.json() } catch (e) { data = null } return { status: resp.status, data } } const ok = (name) => { passed++; console.log(' ✔ ' + name) } const bad = (name, e) => { failed++; console.log(' ✘ ' + name + ' —— ' + (e || 'fail')) } const chk = (cond, msg) => { if (!cond) throw new Error(msg) } const main = async () => { await new Promise(res => server.listen(0, res)) // 1) lab 列表包含 bot_chess(首页页签渲染) { const r = await req('GET', '/api/thought_lab/labs') chk(r.data && r.data.ok, 'labs 接口不 ok') const lab = (r.data.labs || []).find(l => l.id === 'bot_chess') chk(!!lab, '列表无 bot_chess') ok('lab 列表包含 bot_chess(' + lab.name + ')') } // 2) 创建比赛 let matchId = 0, redToken = '', blackToken = '' { const r = await req('POST', '/api/thought_lab/bot_chess/matches', { title: '冒烟测试', red_bot: '红Bot', black_bot: '黑Bot', best_of: 1 }) chk(r.data && r.data.ok, '创建比赛失败:' + JSON.stringify(r.data)) chk(r.data.match_id > 0, '无 match_id') chk(r.data.red_token && r.data.black_token, '未返回 token') matchId = r.data.match_id redToken = r.data.red_token blackToken = r.data.black_token ok(`创建比赛 #${matchId}(红/黑 token 已签发)`) } // 3) 未开局时走子 → MATCH_NOT_STARTED { const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'h2-e2', side: 'red' }) chk(r.status === 400 && r.data.code === 'MATCH_NOT_STARTED', '未开局应拒绝走子:' + JSON.stringify(r.data)) ok('未开局走子被拒(MATCH_NOT_STARTED)') } // 4) 开局 { const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/start`) chk(r.data && r.data.ok, '开局失败') ok('开局成功') } // 5) 红方合法走子 h2-e2 { const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'h2e2', side: 'red' }) chk(r.data && r.data.ok && r.data.accepted, '红方 h2e2 应被接受:' + JSON.stringify(r.data)) ok('红方 h2e2 合法走子被接受') } // 6) 轮次保护:红方再次走子 → NOT_YOUR_TURN { const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'b0c2', side: 'red' }) chk(r.data && r.data.code === 'NOT_YOUR_TURN', '轮次保护失效:' + JSON.stringify(r.data)) ok('红方连走被拒(NOT_YOUR_TURN)') } // 7) 非法走子:黑马斜走 b9c8(马走日,斜线非法)→ 400 + 错误码 { const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'b9c8', side: 'black' }) chk(r.status === 400 && r.data.code === 'ILLEGAL_MOVE', '非法走子应 400+code:' + JSON.stringify(r.data)) ok('非法走子返回 400 + ILLEGAL_MOVE') } // 8) bot token 鉴权:裸 token 走子/查状态 { const r = await req('GET', `/api/thought_lab/bot_chess/matches/${matchId}/state`) chk(r.data && r.data.ok, 'state 失败') chk(r.data.board && r.data.board.fen.includes(' '), 'state 无 FEN') chk(r.data.game && (r.data.game.side_to_move === 'red' || r.data.game.side_to_move === 'black'), 'state 无 side_to_move') ok('state 接口返回局面(FEN/棋盘/turn/side_to_move)') } // 9) 黑方合法走子 + 状态推进 { const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'h9g7', side: 'black' }) chk(r.data && r.data.ok, '黑方 h9g7 应接受:' + JSON.stringify(r.data)) ok('黑方 h9g7 合法走子被接受') } // 10) MD 日志可导出 { const resp = await fetch(`http://127.0.0.1:${server.address().port}/api/thought_lab/bot_chess/matches/${matchId}/log.md`, { headers: { Cookie: COOKIE } }) const md = await resp.text() chk(resp.status === 200 && md.includes('机器人象棋 比赛日志'), 'MD 日志异常') chk(md.includes('h2'), 'MD 日志应包含着法 h2') ok('MD 日志导出成功(含着法记录)') } // 11) 清理 { const r = await req('DELETE', `/api/thought_lab/bot_chess/matches/${matchId}`) chk(r.data && r.data.ok, '删除失败') // token 不应再被识别 const r2 = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'h2e2', side: 'red' }) chk(r2.status === 404 || !r2.data.ok, '删除后仍可访问') ok('比赛删除成功') } server.close() console.log(`\n结果:${passed} 通过 / ${failed} 失败`) process.exit(failed ? 1 : 0) } main().catch(e => { console.error('冒烟测试异常:', e); server && server.close(); process.exit(1) })