/** * 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'); }); });