Files
fmq d16b3d3cdc feat(all): 数据库模块化拆分与版本化迁移、任务引擎命名体系收敛、物理输出校验加固与用户配置接通
- server/db: 拆 4929 行 db.rs 单体为 db/ 目录,migrations.rs 引入 PRAGMA user_version
    版本化迁移运行器(M1~M13)
  - 任务引擎 Phase 6/7b/7c 改名收敛:EngineStageConfig→PhaseConfig、StagePolicy→ResumePolicy、
    Converged→Completed、删除 task_type 列、success_method 拆 tlusty_/synspec_ 双列、
    新增 tlusty_status/synspec_status 半失败阶段守卫
  - 科学正确性加固:conv_check 任意行 NaN/Inf/溢出判无效(0 行容忍)、新增 spec_is_valid
    校验 SYNSPEC 脏谱、itek_history 逐次迭代全量保真、fmt_abn powf 溢出饱和
  - 用户配置真正接通:tlusty_chain/tlusty_input 由死字段经 调度器→TaskSpec→executor→runner
    透传生效;config 加载期 validate + deny_unknown_fields + 解析失败记 warn
  - 调度修复:H1 活锁(pending_strategies 跳过已失败策略)、种子查找错误不再静默降级冷启动
  - dashboard: 阶段配置面板 tlusty_stage/synspec_stage、"已完成"标签、迭代诊断展示
  - docs: 新增 database_refactor_design.md,同步 database/api/PIPELINE/workflow_detail
2026-08-06 20:51:21 +08:00

86 lines
3.1 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.
/* DCTS 平行集合图 sessionStorage 缓存单测(node:test,零依赖)
*
* 覆盖 parSets.js 的 savePsCache / restorePsCache:刷新页面后恢复数据、单槽位清理、
* 损坏数据回退。node 无 sessionStorage,测试前用内存 Map stub。
*/
import { test } from 'node:test';
import assert from 'node:assert/strict';
// 内存 sessionStorage stubsetItem 可注入配额错误用于 QuotaExceeded 分支)
let store = new Map();
let quotaExceeded = false;
globalThis.sessionStorage = {
get length() { return store.size; },
key: (i) => [...store.keys()][i] ?? null,
getItem: (k) => (store.has(k) ? store.get(k) : null),
setItem: (k, v) => {
if (quotaExceeded) throw new Error('QuotaExceededError');
store.set(k, String(v));
},
removeItem: (k) => { store.delete(k); },
};
const { savePsCache, restorePsCache } = await import('../src/views/detail/parSets.js');
function ctx(name, points = [], ts = null) {
return { name, psState: { points, ts } };
}
test('savePsCache 写入 JSON 并可由 restorePsCache 恢复', () => {
store.clear();
const before = Date.now();
const c = ctx('sdB_cno', [{ name: 'p1', teff: 20000, status: 'completed' }]);
savePsCache(c);
// 持久化 key 存在
assert.ok(store.has('dcts_ps_points_sdB_cno'));
// 恢复后 points 一致;快照时间由 savePsCache 记录为保存时刻(Date.now 附近)
const c2 = ctx('sdB_cno');
assert.equal(restorePsCache(c2), true);
assert.deepEqual(c2.psState.points, c.psState.points);
assert.ok(Number.isFinite(c2.psState.ts) && c2.psState.ts >= before);
assert.ok(Math.abs(c2.psState.ts - Date.now()) < 5000);
});
test('多工作流缓存并存:切换工作流不删除彼此的缓存', () => {
store.clear();
savePsCache(ctx('wf_a', [{ name: 'a' }]));
savePsCache(ctx('wf_b', [{ name: 'b' }]));
savePsCache(ctx('wf_a', [{ name: 'a2' }])); // 再回 wf_a 更新,writes 覆盖自身 key
// wf_a 与 wf_b 的缓存都保留
assert.ok(store.has('dcts_ps_points_wf_a'));
assert.ok(store.has('dcts_ps_points_wf_b'));
// 各自恢复出各自的数据(不串)
const ca = ctx('wf_a');
const cb = ctx('wf_b');
assert.equal(restorePsCache(ca), true);
assert.equal(restorePsCache(cb), true);
assert.deepEqual(ca.psState.points, [{ name: 'a2' }]); // 更新后是最新值
assert.deepEqual(cb.psState.points, [{ name: 'b' }]);
});
test('无缓存 / 空 key 时 restorePsCache 返回 false', () => {
store.clear();
const c = ctx('no_such_wf');
assert.equal(restorePsCache(c), false);
assert.deepEqual(c.psState.points, []);
});
test('损坏的 JSON / 非数组数据返回 false(降级为重新拉取)', () => {
store.clear();
store.set('dcts_ps_points_bad_json', 'not-json{{{');
store.set('dcts_ps_points_bad_shape', JSON.stringify({ foo: 1 }));
assert.equal(restorePsCache(ctx('bad_json')), false);
assert.equal(restorePsCache(ctx('bad_shape')), false);
});
test('QuotaExceeded 时 savePsCache 静默失败,不抛错', () => {
store.clear();
quotaExceeded = true;
assert.doesNotThrow(() => savePsCache(ctx('big_wf', new Array(10000).fill({ x: 1 }))));
quotaExceeded = false;
});