graph-db-prod-guard.test.js
4.01 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
73
74
75
76
77
78
79
80
/**
* Unit Tests — **测试进程不许打开共享生产库 `data/graph.sqlite`**
*
* 守的是 `graph-db.getDb()` 里的 `assertNotProdDbInTests()`。
*
* ── 为什么再加一条 ─────────────────────────────────────────────────────
* `test-isolation-env.test.js` 已经守了同一族缺陷,但它的判据是
* 「测试设了 XXX_PATH / XXX_DIR,生产代码必须真的读它」—— 那挡的是**设了但没接通**。
* 这次漏进来的是另一个形态:`agent-chat-tenant-isolation.test.js` **一个字都没设**,
* 于是那条守卫没有可查的东西,而 `/task/dispatch` 每跑一次往生产库落
* 2 条 goal + 2 条 task。攒到 25 条 `_global` goal 才被人翻库发现,期间测试一直全绿。
*
* 静态扫源码判不出「这个测试会不会开库」(要跨 require 链推导),所以这条守卫改成
* **运行期闸门 + 对它本身的黑盒验证**:起一个真子进程去撞,看撞不撞得动。
*
* ── 判据 ───────────────────────────────────────────────────────────────
* 只在 `process.env.VITEST` 为真时生效,只在**真正开库那一刻**判(不是 require 时)——
* 只为取一个常量而 require 本模块是合法用法,顶层抛会误伤(goal-task-model.test.js 就这么用)。
*/
import { describe, it, expect } from 'vitest';
import { execFileSync } from 'child_process';
import fs from 'fs';
import os from 'os';
import path from 'path';
const ROOT = path.resolve(__dirname, '../../..');
const REQUIRE_DB = `require(${JSON.stringify(path.join(ROOT, 'server/services/graph-db.js'))})`;
/** 在干净子进程里跑一小段,返回 {code, stderr}。不继承本进程的 VITEST / GRAPH_DB_PATH。 */
function run(code, env) {
const base = { ...process.env };
delete base.VITEST;
delete base.GRAPH_DB_PATH;
try {
execFileSync(process.execPath, ['-e', code], {
env: { ...base, ...env },
stdio: ['ignore', 'pipe', 'pipe'],
cwd: ROOT,
});
return { code: 0, stderr: '' };
} catch (e) {
return { code: e.status, stderr: String(e.stderr || '') };
}
}
describe('graph-db · 测试进程不许开生产库', () => {
it('VITEST=1 且没设 GRAPH_DB_PATH → getDb() 抛,且报错里给得出修法', () => {
const r = run(`${REQUIRE_DB}.getDb()`, { VITEST: '1' });
expect(r.code).not.toBe(0);
expect(r.stderr).toContain('测试进程试图打开共享生产库');
// 光抛不够 —— 撞上的人要能照着改,所以报错必须点名那个 env 和「要在 require 之前设」
expect(r.stderr).toContain('GRAPH_DB_PATH');
expect(r.stderr).toContain('require');
});
it('VITEST=1 但设了 GRAPH_DB_PATH → 正常开一次性库(判据不是「测试里一律不许开库」)', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'lemonboss-graphdb-guard-'));
const dbPath = path.join(dir, 'graph.sqlite');
try {
const r = run(`${REQUIRE_DB}.getDb()`, { VITEST: '1', GRAPH_DB_PATH: dbPath });
expect(r.stderr).toBe('');
expect(r.code).toBe(0);
expect(fs.existsSync(dbPath)).toBe(true);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
it('不在测试进程里(无 VITEST)→ 闸门不生效,生产照常开库', () => {
// 这一条防的是把闸门写死成「永远不许开 data/graph.sqlite」——那会把生产也一起锁死。
const r = run(`${REQUIRE_DB}.getDb(); console.log('opened')`, {});
expect(r.code).toBe(0);
});
it('样本非空:这个仓库里真的存在会开库的路径', () => {
// 少了这条,上面三条在「模块路径写错、require 直接失败」时也会以「抛了异常」的形式变绿。
const mod = require('../../services/graph-db.js');
expect(typeof mod.getDb).toBe('function');
});
});