ec02a791ee
- R-027 静态化:策略恒为内置两项(不可增删改名,保留显隐/排序);设置页移除「策略分组」; 字段配置入口迁到策略 tab 内「列设置」旁(FieldConfigDialog + schema-update 单策略写入) - R-026 计算字段:自写公式引擎(中文变量、四则/括号/round·abs·min·max、缺值短路→—)+ 变量目录(行情/合约/持仓/自定义字段)+ strategy-positions 逐行现算(只读内存缓存,不落库、列只读) - R-028 判定型:比较运算 + and/or + inferResultKind + 结果类型一致性校验 + 判定列 ✓/— 渲染; 网格超市落地 可下空单=涨停价>基准值+网格大小、可下多单=跌停价<基准值-网格大小 - 变量选择改标签平铺(老师反馈);公式手册 docs/99-其他材料/计算字段公式说明.md - 约束同步:产品约束-002/003/004/009/013/014、技术约束-014/015/022/023/024、UI约束-002/003/005/008 - 回归:新增 test-r026/test-r027,更新 r011/r013/r017,17 个脚本全绿;typecheck/build 通过
107 lines
5.6 KiB
JavaScript
107 lines
5.6 KiB
JavaScript
/**
|
||
* R-011 API 层集成测试:handleStrategy tabs 域(R-027 策略静态化更新)
|
||
* 独立内存 mock settings scope + mock manager,验证:
|
||
* 1. tabs 端点:返回归一化统一数组(恒 5 条:3 内置 + 2 策略)
|
||
* 2. tabs/update:整表更新(顺序 + 显隐)
|
||
* 3. strategies/add:已退役(方法表不含 + 调用抛 not-found)
|
||
* 4. strategies/remove:已退役(方法表不含 + 调用抛 not-found)
|
||
* 5. strategies/update:已退役(方法表不含 + 调用抛 not-found)
|
||
* 6. strategies/schema-update:单策略字段定义写入(读回一致)
|
||
* 7. 端点表不含 strategies/move(已废弃)
|
||
* 纯内存,不触真实数据(技术约束-011)
|
||
*/
|
||
import { handleStrategy, STRATEGY_METHODS } from '../src/api/strategies.js';
|
||
|
||
let pass = 0, fail = 0;
|
||
function assert(cond, msg) {
|
||
if (cond) { pass++; console.log(" ✅ " + msg); }
|
||
else { fail++; console.log(" ❌ " + msg); }
|
||
}
|
||
|
||
/** 内存 mock scope */
|
||
function makeScope(initial) {
|
||
const store = structuredClone(initial ?? {});
|
||
return {
|
||
get: () => structuredClone(store),
|
||
update: async (patch) => { Object.assign(store, structuredClone(patch)); },
|
||
};
|
||
}
|
||
|
||
/** 断言某端点已退役:STRATEGY_METHODS 不含 + 调用抛 code=not-found */
|
||
async function assertRetired(method, args, runtime) {
|
||
assert(!STRATEGY_METHODS.has(method), "STRATEGY_METHODS 不含 " + method);
|
||
try {
|
||
await handleStrategy(method, args, runtime);
|
||
assert(false, method + " 应抛 not-found(未抛错)");
|
||
} catch (e) {
|
||
assert(e.code === "not-found", method + " 抛 code=not-found");
|
||
}
|
||
}
|
||
|
||
const scope = makeScope({
|
||
strategies: [
|
||
{ id: "grid-supermarket", name: "网格超市" },
|
||
{ id: "manual-t", name: "手动做T" },
|
||
],
|
||
tabs: [
|
||
{ id: "tab-all-positions", kind: "builtin", refKey: "allPositions", name: "全部持仓", visible: true, order: 0 },
|
||
{ id: "tab-trade-records", kind: "builtin", refKey: "tradeRecords", name: "交易记录", visible: true, order: 1 },
|
||
{ id: "tab-watchlist", kind: "builtin", refKey: "watchlist", name: "关注列表", visible: true, order: 2 },
|
||
{ id: "tab-strategy-grid-supermarket", kind: "strategy", refId: "grid-supermarket", name: "网格超市", visible: true, order: 3 },
|
||
{ id: "tab-strategy-manual-t", kind: "strategy", refId: "manual-t", name: "手动做T", visible: true, order: 4 },
|
||
],
|
||
});
|
||
const runtime = { manager: {}, settings: scope };
|
||
|
||
// 1. tabs 端点
|
||
console.log("\n[1] tabs 端点");
|
||
const strat1 = await handleStrategy("strategies", {}, runtime);
|
||
assert(Array.isArray(strat1) && strat1.length === 2, "strategies 端点恒 2 项(内置)");
|
||
const tabs1 = await handleStrategy("tabs", {}, runtime);
|
||
assert(Array.isArray(tabs1) && tabs1.length === 5, "tabs 恒 5 条(3 内置 + 2 策略)");
|
||
assert(tabs1.filter((t) => t.kind === "builtin").length === 3 && tabs1.filter((t) => t.kind === "strategy").length === 2, "内置 3 + 策略 2 混排");
|
||
assert(tabs1.some((t) => t.refId === "grid-supermarket") && tabs1.some((t) => t.refId === "manual-t"), "策略 tab 恒在(不可增删)");
|
||
|
||
// 2. tabs/update 整表更新
|
||
console.log("\n[2] tabs/update 整表更新");
|
||
const afterUpdate = await handleStrategy("tabs/update", { tabs: [
|
||
{ id: "tab-strategy-manual-t", kind: "strategy", refId: "manual-t", name: "手动做T", visible: false, order: 0 },
|
||
{ id: "tab-all-positions", kind: "builtin", refKey: "allPositions", name: "全部持仓", visible: true, order: 1 },
|
||
{ id: "tab-watchlist", kind: "builtin", refKey: "watchlist", name: "关注列表", visible: true, order: 2 },
|
||
{ id: "tab-strategy-grid-supermarket", kind: "strategy", refId: "grid-supermarket", name: "网格超市", visible: true, order: 3 },
|
||
{ id: "tab-trade-records", kind: "builtin", refKey: "tradeRecords", name: "交易记录", visible: true, order: 4 },
|
||
] }, runtime);
|
||
assert(afterUpdate[0].id === "tab-strategy-manual-t" && afterUpdate[0].visible === false, "手动做T 排最前且隐藏(顺序+显隐生效)");
|
||
assert(afterUpdate[1].id === "tab-all-positions", "内置 tab 排第二位");
|
||
assert(afterUpdate.length === 5, "整表更新后恒 5 条");
|
||
|
||
// 3. strategies/add 已退役
|
||
console.log("\n[3] strategies/add 退役");
|
||
await assertRetired("strategies/add", { name: "长线持有" }, runtime);
|
||
|
||
// 4. strategies/remove 已退役
|
||
console.log("\n[4] strategies/remove 退役");
|
||
await assertRetired("strategies/remove", { strategyId: "grid-supermarket" }, runtime);
|
||
|
||
// 5. strategies/update 已退役
|
||
console.log("\n[5] strategies/update 退役");
|
||
await assertRetired("strategies/update", { strategies: [] }, runtime);
|
||
|
||
// 6. strategies/schema-update 单策略写入(R-027 唯一写路径)
|
||
console.log("\n[6] strategies/schema-update");
|
||
const saved = await handleStrategy("strategies/schema-update", {
|
||
strategyId: "grid-supermarket",
|
||
configSchema: [{ key: "gridGap", label: "网格间距", type: "text", def: "3%" }],
|
||
}, runtime);
|
||
assert(saved.configSchema[0].label === "网格间距", "schema-update 返回更新后的策略对象");
|
||
const stratAfter = await handleStrategy("strategies", {}, runtime);
|
||
assert(stratAfter.find((s) => s.id === "grid-supermarket").configSchema[0].label === "网格间距", "读回一致(写入 strategyFields)");
|
||
assert(stratAfter.find((s) => s.id === "manual-t").configSchema.length === 0, "只影响目标策略(manual-t 无定义)");
|
||
|
||
// 7. 端点表不含 strategies/move
|
||
console.log("\n[7] strategies/move 已废弃");
|
||
assert(!STRATEGY_METHODS.has("strategies/move"), "STRATEGY_METHODS 不含 strategies/move");
|
||
|
||
console.log("\n结果: " + pass + " 通过 / " + fail + " 失败");
|
||
if (fail > 0) process.exit(1);
|