tasks-executor-migration.test.js
3.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 批 5 · `tasks` 表吸收三列(executor / assignee_agent_id / parent_task_id)的**迁移路径**。
*
* 为什么单独钉这一条:加列时我把 `CREATE INDEX ... ON tasks(assignee_agent_id)` 写进了建表块,
* 而建表块整体先于迁移块执行 —— 存量库上 `CREATE TABLE IF NOT EXISTS tasks` 是 no-op,
* 表还是加列前的形态,那一句索引直接抛「no such column」,`getDb()` 起不来。
* 后果不是少个功能,是**所有存量库一启动就炸**,而新建库(单测默认走的那条路)完全正常 ——
* 只测新建库的话这个洞看不见。
*/
import { describe, it, expect } from 'vitest';
import { createRequire } from 'module';
import os from 'os';
import path from 'path';
const require_ = createRequire(import.meta.url);
const NEW_COLS = ['executor', 'assignee_agent_id', 'parent_task_id'];
/** 用独立 DB 文件跑一遍 getDb(),返回该库的 work-graph-db 模块实例 */
function freshDb(dbPath) {
process.env.GRAPH_DB_PATH = dbPath;
for (const m of ['../../services/work-graph-db', '../../services/graph-db']) {
delete require_.cache[require_.resolve(m)];
}
const wdb = require_('../../services/work-graph-db');
wdb.getDb();
return wdb;
}
describe('tasks 表的批 5 迁移', () => {
it('存量库(没有这三列)重启后能补上,且不炸', () => {
const dbPath = path.join(os.tmpdir(), `b5-mig-${Date.now()}-${Math.floor(process.hrtime()[1] / 1e3)}.sqlite`);
const wdb = freshDb(dbPath);
// 退回加列之前的形态:索引先删(它引用了要删的列),再删三列
const Database = require_('better-sqlite3');
const raw = new Database(dbPath);
raw.exec('DROP INDEX IF EXISTS idx_tasks_assignee');
for (const c of NEW_COLS) raw.exec(`ALTER TABLE tasks DROP COLUMN ${c}`);
const before = raw.pragma('table_info(tasks)').map((c) => c.name);
raw.close();
for (const c of NEW_COLS) expect(before).not.toContain(c);
// 重开进程 = 服务重启:迁移必须把三列和索引都补回来,且不抛
const wdb2 = freshDb(dbPath);
const cols = wdb2.getDb().pragma('table_info(tasks)');
const names = cols.map((c) => c.name);
for (const c of NEW_COLS) expect(names).toContain(c);
// 旧行的落点:executor 缺省 'graph'(加列前建的全是图任务)
expect(cols.find((c) => c.name === 'executor').dflt_value).toBe("'graph'");
// 索引必须建在 ALTER 之后 —— 顺序反了就是上面注释里那个「存量库一启动就炸」
const idx = wdb2.getDb()
.prepare("SELECT 1 AS ok FROM sqlite_master WHERE type='index' AND name='idx_tasks_assignee'")
.get();
expect(idx?.ok).toBe(1);
expect(wdb).toBeTruthy(); // 前一个实例只是用来建库,这里避免 lint 抱怨未使用
});
it('新建库直接就是目标形态(三列 + 索引齐全)', () => {
const dbPath = path.join(os.tmpdir(), `b5-new-${Date.now()}-${Math.floor(process.hrtime()[1] / 1e3)}.sqlite`);
const wdb = freshDb(dbPath);
const names = wdb.getDb().pragma('table_info(tasks)').map((c) => c.name);
for (const c of NEW_COLS) expect(names).toContain(c);
expect(wdb.getDb().prepare("SELECT 1 AS ok FROM sqlite_master WHERE type='index' AND name='idx_tasks_assignee'").get()?.ok).toBe(1);
});
});