feat: 新增思想实验室模块与配套功能
- 新增5个物理仿真实验室:黑洞模拟、日地轨道、3D太阳系、π推导、拉格朗日点 - 新增思想实验室后端路由与鉴权逻辑 - 首页新增思想实验室页签与前端渲染逻辑 - 修复中韩半导体监控标的配置错误 - 新增市场技能推送白名单过滤逻辑与单元测试
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 单元测试:market_skills manager 发送侧白名单二次过滤(filterPushRows)
|
||||
* 运行:node dev_test_scripts/unit/test_manager_filter_push_rows.js(cwd 必须为项目根目录)
|
||||
*
|
||||
* 覆盖场景:
|
||||
* 1. 中韩半导体指:symbol 误配 sh000688 与科创50 撞车(在白名单内),必须被排除名单拦截
|
||||
* 2. 白名单内 skill(科创50/纳斯达克/COMEX黄金)放行
|
||||
* 3. 白名单外 skill(COMEX白银 hf_SI)拦截
|
||||
* 4. 排除名单 skill(巴菲特指)拦截
|
||||
* 5. 无法识别 symbol 的存量记录默认拦截
|
||||
* 6. 空数组 / null 不崩溃
|
||||
*/
|
||||
const assert = require('assert')
|
||||
const manager = require('../../src/server/market_skills/manager')
|
||||
|
||||
const rows = [
|
||||
{ skill: '中韩半导体指', title: '涨幅预警' },
|
||||
{ skill: '科创50', title: '涨幅预警' },
|
||||
{ skill: '纳斯达克', title: '涨幅预警' },
|
||||
{ skill: 'COMEX黄金', title: '价格预警' },
|
||||
{ skill: 'COMEX白银', title: '价格预警' },
|
||||
{ skill: '巴菲特指', title: '极端高估' },
|
||||
{ skill: '不存在的skill', title: 'x' }
|
||||
]
|
||||
|
||||
const out = manager.filterPushRows(rows)
|
||||
const names = out.map(r => r.skill)
|
||||
assert.deepStrictEqual(names, ['科创50', '纳斯达克', 'COMEX黄金'], '发送侧过滤结果不符:' + JSON.stringify(names))
|
||||
|
||||
assert.deepStrictEqual(manager.filterPushRows([]), [])
|
||||
assert.deepStrictEqual(manager.filterPushRows(null), [])
|
||||
|
||||
console.log('PASS test_manager_filter_push_rows')
|
||||
process.exit(0)
|
||||
@@ -65,5 +65,24 @@ assert(r4.kept.length === mockQuotes.length, '缺省字段不过滤')
|
||||
const r5 = applyNotifyAllowlist(null, { notify_symbol_allowlist: ALLOWLIST })
|
||||
assert(Array.isArray(r5.kept) && r5.kept.length === 0, 'quotes 为 null 时不崩溃')
|
||||
|
||||
console.log('== test: manager.isSkillPushAllowed(skill 推送通道白名单) ==')
|
||||
|
||||
const { isSkillPushAllowed } = require(path.join(process.cwd(), 'src', 'server', 'market_skills', 'manager.js'))
|
||||
|
||||
// 6. 白名单内 skill 放行(8 个保留项)
|
||||
assert(['sh000001', 'sz399001', 'sh000300', 'sh000688', 'sz399006', 'usINX', 'usIXIC', 'hf_GC'].every(s => isSkillPushAllowed(s, ALLOWLIST)), '白名单内 8 个 symbol 全部放行')
|
||||
|
||||
// 7. 白名单外 skill 拦截(截图里仍推送的几个)
|
||||
assert(!isSkillPushAllowed('usEWY', ALLOWLIST), '韩国综合指数 usEWY 被拦截')
|
||||
assert(!isSkillPushAllowed('us_buffett_index', ALLOWLIST), '巴菲特指数 us_buffett_index 被拦截')
|
||||
assert(!isSkillPushAllowed('bj899050', ALLOWLIST), '宝岛加权指数 bj899050 被拦截')
|
||||
assert(!isSkillPushAllowed('sh601988_299', ALLOWLIST), '中国银行-299 sh601988_299 被拦截')
|
||||
assert(!isSkillPushAllowed('hf_SI', ALLOWLIST), 'COMEX白银 hf_SI 被拦截')
|
||||
|
||||
// 8. 大小写不敏感 / 空名单 / null 名单
|
||||
assert(isSkillPushAllowed('USINX', ALLOWLIST), '大小写不敏感')
|
||||
assert(isSkillPushAllowed('usEWY', []), '空名单不限制')
|
||||
assert(isSkillPushAllowed('usEWY', null), 'null 名单不限制')
|
||||
|
||||
console.log(failed === 0 ? '\nALL TESTS PASSED' : `\n${failed} TEST(S) FAILED`)
|
||||
process.exit(failed === 0 ? 0 : 1)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// 单元测试:thought_lab 路由鉴权行为
|
||||
// 运行:node dev_test_scripts/unit/test_thought_lab_api.js
|
||||
// 断言:
|
||||
// 1) GET /api/thought_lab/labs 无 cookie -> 200 且返回 5 个 lab(白名单公开元数据)
|
||||
// 2) lab 页面:无 cookie 无 Referer(直链/外站) -> 401
|
||||
// 3) lab 页面:无 cookie 但同源 Referer(首页导航进入) -> 200
|
||||
// 4) lab 页面:带 nav_gate -> 200
|
||||
const http = require('http')
|
||||
const path = require('path')
|
||||
const express = require(path.join(process.cwd(), 'node_modules', 'express'))
|
||||
const thoughtLab = require(path.join(process.cwd(), 'src', 'server', 'thought_lab'))
|
||||
|
||||
const app = express()
|
||||
thoughtLab.bindRoutes(app)
|
||||
|
||||
const server = app.listen(0, async () => {
|
||||
const port = server.address().port
|
||||
const get = (p, headers) => new Promise((resolve, reject) => {
|
||||
const req = http.get({ host: '127.0.0.1', port, path: p, headers: headers || {} }, res => {
|
||||
let body = ''
|
||||
res.on('data', c => { body += c })
|
||||
res.on('end', () => resolve({ status: res.statusCode, body }))
|
||||
})
|
||||
req.on('error', reject)
|
||||
})
|
||||
|
||||
let fail = 0
|
||||
const check = (name, cond, extra) => {
|
||||
console.log(`${cond ? 'PASS' : 'FAIL'} - ${name}${extra ? ' | ' + extra : ''}`)
|
||||
if (!cond) fail++
|
||||
}
|
||||
|
||||
try {
|
||||
const r1 = await get('/api/thought_lab/labs')
|
||||
let labs = []
|
||||
try { labs = JSON.parse(r1.body).labs || [] } catch {}
|
||||
check('labs 列表无 cookie 应 200', r1.status === 200, `status=${r1.status}`)
|
||||
check('labs 列表应包含 5 个 lab', labs.length === 5, `count=${labs.length}`)
|
||||
|
||||
const page = '/tools/thought_lab/labs/blackhole_sim/index.html'
|
||||
const host = `127.0.0.1:${port}`
|
||||
|
||||
const r2 = await get(page)
|
||||
check('lab 页面直链(无 cookie 无 Referer)应 401', r2.status === 401, `status=${r2.status}`)
|
||||
|
||||
const r3 = await get(page, { referer: `http://${host}/`, host })
|
||||
check('lab 页面同源 Referer(导航进入)应 200', r3.status === 200, `status=${r3.status}`)
|
||||
|
||||
const r4 = await get(page, { cookie: 'nav_gate=1', host })
|
||||
check('lab 页面带 nav_gate 应 200', r4.status === 200, `status=${r4.status}`)
|
||||
|
||||
const r5 = await get(page, { referer: 'https://evil.example.com/', host })
|
||||
check('lab 页面外站 Referer 应 401', r5.status === 401, `status=${r5.status}`)
|
||||
} catch (e) {
|
||||
fail++
|
||||
console.log('FAIL - 请求异常 |', e.message)
|
||||
}
|
||||
|
||||
server.close()
|
||||
console.log(fail === 0 ? 'ALL PASS' : `${fail} FAILED`)
|
||||
process.exit(fail === 0 ? 0 : 1)
|
||||
})
|
||||
Reference in New Issue
Block a user