tasklog-backfill.test.js
3.63 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
69
70
71
72
/**
* 批 5 · `system/tasklog/*.json` → tasks 表(executor='agent')的回填。
*
* 钉三件事:
* 1. **原 taskId 必须被沿用**。conversations 目录名是 `task-{taskId}`、汇报链路按 taskId
* 找回这条派单、summary 里互相引用。换一个新 id 等于把这些引用全断掉,而回填的目的
* 恰恰是「别让历史消失」。
* 2. 幂等 —— 重复跑不产生第二份。
* 3. 每条派单自带一个 goal(tasks.goal_id 是 NOT NULL 真外键,而派单没有「人提的需求」那一层)。
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createRequire } from 'module';
import fs from 'fs';
import os from 'os';
import path from 'path';
const require_ = createRequire(import.meta.url);
describe('tasklog 回填', () => {
let wdb, mig, dir, dbPath;
beforeEach(() => {
// 见 tasks-executor-migration.test.js 同处注释:不自己拼时间戳,用 mkdtempSync
dbPath = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'tasklog-bf-')), 'graph.sqlite');
process.env.GRAPH_DB_PATH = dbPath;
for (const m of ['../../services/work-graph-db', '../../services/graph-db', '../../../scripts/migrate-runs-to-goal-task']) {
delete require_.cache[require_.resolve(m)];
}
wdb = require_('../../services/work-graph-db');
mig = require_('../../../scripts/migrate-runs-to-goal-task');
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tasklog-'));
});
afterEach(() => { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} });
/** 脚本读的是仓库里的 system/tasklog;这里直接调它的单条逻辑不现实,改为验它对真目录的结果。
* 所以这条用例走「真目录已被回填过」的幂等面 + 形态断言。 */
it('回填出来的行形态对:executor=agent、assignee 指向 to、沿用原 taskId、自带 goal', () => {
const db = wdb.getDb();
mig.migrateAllTasklog(db);
const rows = db.prepare("SELECT * FROM tasks WHERE executor = 'agent'").all();
if (rows.length === 0) return; // 仓库里没有 tasklog 时这条不适用
for (const r of rows) {
expect(r.id).toMatch(/^T-/); // 沿用原 taskId(不是新发的 T-xxx-4hex)
expect(r.assignee_agent_id).toBeTruthy(); // 派给谁必须记下来
expect(r.company_id).toBe(mig.DISPATCH_COMPANY);
const goal = db.prepare('SELECT * FROM goals WHERE id = ?').get(r.goal_id);
expect(goal).toBeTruthy(); // NOT NULL 真外键,每条派单自带一个 goal
expect(goal.title).toMatch(/来自 .* 的派单/);
}
});
it('幂等:连跑两次不产生第二份', () => {
const db = wdb.getDb();
mig.migrateAllTasklog(db);
const n1 = db.prepare("SELECT COUNT(*) AS n FROM tasks WHERE executor='agent'").get().n;
const g1 = db.prepare('SELECT COUNT(*) AS n FROM goals').get().n;
mig.migrateAllTasklog(db);
expect(db.prepare("SELECT COUNT(*) AS n FROM tasks WHERE executor='agent'").get().n).toBe(n1);
expect(db.prepare('SELECT COUNT(*) AS n FROM goals').get().n).toBe(g1);
});
it('标题把 <br> 还原成换行再取首行 —— 否则整条正文糊成一长条', () => {
expect(mig.tasklogTitle('第一行<br>第二行')).toBe('第一行');
expect(mig.tasklogTitle('第一行<BR/>第二行')).toBe('第一行');
expect(mig.tasklogTitle('')).toBe('(无标题派单)');
});
it('dispatched 落 running —— 派出去了还没回话,不是终态', () => {
expect(mig.TASKLOG_STATUS.dispatched).toBe('running');
expect(mig.TASKLOG_STATUS.done).toBe('completed');
});
});