/** * Unit Tests — verifyCounts 必须镜像 seedCompanyAgents 的 meta-bootstrap 过滤 * * `seedCompanyAgents` 对 `type: 'meta-bootstrap'` 的公司角色是 `continue`: * 既不写 org_graph_nodes 也不写 system_agents。而 `verifyCounts` 复算期望值时 * 原先把 `company.json` 的 `agents[]` **全部**算进去 —— 于是任何一家公司的 * agents[] 里出现一个 meta-bootstrap 角色,对账就确定性变红。 * * 不是假设:用户手工走查建的 `mycompany` 有 10 个角色,其中 `orggraph-planning` * 是 meta-bootstrap,对账报 `expected 10 / actual 9`。也就是说这条缺陷是被 * **正常使用**触发的 —— 界面上点一遍新建公司就会踩到。 * * 这个文件单独存在(不并进 graph-seed-service.test.js)的原因:那边 hermetic 块的 * 断言钉着精确的行数/节点数,往它的夹具里加一个角色会连带改掉既有断言。 * 这里用 `GRAPH_SEED_ROOT` 自建一个只有两个角色的最小夹具,互不干扰。 */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import fs from 'fs'; import os from 'os'; import path from 'path'; const MODULES = ['../../services/graph-db', '../../services/graph-seed-service']; describe('verifyCounts × meta-bootstrap 过滤', () => { let seed; let root; let dbPath; const COMPANY = 'co-mb'; const NORMAL_ROLE = 'org-planner'; const BOOTSTRAP_ROLE = 'orggraph-planning'; beforeAll(() => { root = fs.mkdtempSync(path.join(os.tmpdir(), 'seed-mb-root-')); dbPath = path.join(root, 'graph.sqlite'); // 最小夹具:system/agents//{agent.md, config.json} + system/agents/_registry.json // + data/companies//company.json // // config.json 放在**本体**里(28 批次 4):公司不再持有 agent 实体, // isMetaBootstrap() 读的就是本体那一份。放回公司侧的话它读不到、恒返回 false, // 症状正是本文件要防的那个 —— meta-bootstrap 角色被算进期望值。 for (const [id, type] of [[NORMAL_ROLE, 'executor'], [BOOTSTRAP_ROLE, 'meta-bootstrap']]) { const d = path.join(root, 'system', 'agents', id); fs.mkdirSync(d, { recursive: true }); fs.writeFileSync(path.join(d, 'agent.md'), `# ${id}\n`); fs.writeFileSync(path.join(d, 'config.json'), JSON.stringify({ type })); } fs.writeFileSync(path.join(root, 'system', 'agents', '_registry.json'), JSON.stringify({})); const companyDir = path.join(root, 'data', 'companies', COMPANY); fs.mkdirSync(companyDir, { recursive: true }); fs.writeFileSync( path.join(companyDir, 'company.json'), JSON.stringify({ id: COMPANY, name: COMPANY, agents: [NORMAL_ROLE, BOOTSTRAP_ROLE] }) ); process.env.GRAPH_SEED_ROOT = root; process.env.GRAPH_DB_PATH = dbPath; for (const m of MODULES) delete require.cache[require.resolve(m)]; seed = require('../../services/graph-seed-service'); }); afterAll(() => { delete process.env.GRAPH_SEED_ROOT; delete process.env.GRAPH_DB_PATH; try { fs.rmSync(root, { recursive: true, force: true }); } catch {} for (const m of MODULES) delete require.cache[require.resolve(m)]; }); it('种完之后对账干净:meta-bootstrap 角色不算进期望值', () => { // runStartupSeed 只做 reconcile + seedAgents,**不含** seedCompanyAgents // (两个 server 入口启动时也不种公司角色)—— 所以公司那部分要显式调。 // 少了这一行的话对账会报 expected 1 / actual 0,看着像本条要防的那个 bug, // 其实是夹具没种东西:两种原因症状相同,所以下面第二条用例专门把它们分开。 seed.runStartupSeed(); seed.seedCompanyAgents(); const v = seed.verifyCounts(); // 期望值把 meta-bootstrap 算进去时,这里会是 // [{check: "org_graph_nodes COUNT(*) WHERE company_id='co-mb'", expected: 2, actual: 1}] expect(v.mismatches).toEqual([]); expect(v.ok).toBe(true); }); it('这个夹具确实覆盖到了那条分支(否则上一条是空跑)', () => { const db = require('../../services/graph-db').getDb(); const rows = db .prepare(`SELECT role_name FROM org_graph_nodes WHERE company_id = ? ORDER BY role_name`) .all(COMPANY) .map((r) => r.role_name); // 普通角色种进去了、meta-bootstrap 角色没有 —— 两侧都断言, // 只断言「对账干净」的话,seed 一个都没种时它也会“通过” expect(rows).toEqual([NORMAL_ROLE]); }); });