3ba267b38a
- 优化服务端百炼API配置,将硬编码的API地址改为通过环境变量读取,支持自定义部署端点 - 新增机器人象棋思想实验室,包含完整对战逻辑、管理接口与前端页面,配套测试用例 - 优化思想实验室路由注册,新增公开路由绑定能力支持非浏览器客户端调用
140 lines
5.7 KiB
JavaScript
140 lines
5.7 KiB
JavaScript
// ============================================================
|
||
// 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) }) |