- 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
86 lines
3.1 KiB
JavaScript
86 lines
3.1 KiB
JavaScript
/* DCTS 平行集合图 sessionStorage 缓存单测(node:test,零依赖)
|
||
*
|
||
* 覆盖 parSets.js 的 savePsCache / restorePsCache:刷新页面后恢复数据、单槽位清理、
|
||
* 损坏数据回退。node 无 sessionStorage,测试前用内存 Map stub。
|
||
*/
|
||
|
||
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
|
||
// 内存 sessionStorage stub(setItem 可注入配额错误用于 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;
|
||
});
|