Files
Toolbox/dev_test_scripts/integration/test_bot_chess_api_smoke.js
yangxiangyuan 3ba267b38a feat(server, thought-lab): 优化百炼API配置并新增机器人象棋实验室
- 优化服务端百炼API配置,将硬编码的API地址改为通过环境变量读取,支持自定义部署端点
- 新增机器人象棋思想实验室,包含完整对战逻辑、管理接口与前端页面,配套测试用例
- 优化思想实验室路由注册,新增公开路由绑定能力支持非浏览器客户端调用
2026-08-18 13:23:36 +08:00

140 lines
5.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// 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) })