graph-service.test.js 5.64 KB
/**
 * Unit Tests — graph-service.js (P1)
 *
 * Uses GRAPH_DB_PATH env override (same pattern as graph-db.test.js /
 * graph-seed-service.test.js) so the real data/graph.sqlite is never touched.
 */
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import fs from 'fs';
import path from 'path';

describe('graph-service', () => {
  let graphDb;
  let graphService;
  const TEST_DB_PATH = '/tmp/test-graph-service.sqlite';

  beforeAll(() => {
    if (fs.existsSync(TEST_DB_PATH)) fs.unlinkSync(TEST_DB_PATH);
    process.env.GRAPH_DB_PATH = TEST_DB_PATH;
    delete require.cache[require.resolve('../../services/graph-db')];
    delete require.cache[require.resolve('../../services/graph-service')];
    graphDb = require('../../services/graph-db');
    graphService = require('../../services/graph-service');
  });

  afterAll(() => {
    try { fs.unlinkSync(TEST_DB_PATH); } catch {}
    try { fs.unlinkSync(`${TEST_DB_PATH}-shm`); } catch {}
    try { fs.unlinkSync(`${TEST_DB_PATH}-wal`); } catch {}
    delete process.env.GRAPH_DB_PATH;
  });

  beforeEach(() => {
    // Fresh tables between tests so assertions don't depend on ordering.
    const db = graphDb.getDb();
    db.exec('DELETE FROM org_graph_nodes; DELETE FROM system_agents;');
  });

  function insertSystemAgent({ id, origin = 'builtin', source_kind = 'global_agent', legacy_path = `system/agents/${id}`, scope = 'public', owner_company_id = null }) {
    const db = graphDb.getDb();
    const now = new Date().toISOString();
    db.prepare(`
      INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, 'active', NULL, ?, ?)
    `).run(id, id, scope, owner_company_id, source_kind, origin, legacy_path, now, now);
  }

  describe('lookupSharedAgentMd', () => {
    it('路径由 id 推导,不再读 legacy_path(本体地址恒为 system/agents/{id})', () => {
      // legacy_path 故意写成一个早就不存在的老地址:读侧不许再依赖它。
      insertSystemAgent({ id: 'qa-judge', legacy_path: 'system/templates/qa-judge' });

      const result = graphService.lookupSharedAgentMd('qa-judge');

      expect(result).toBe(path.resolve(__dirname, '../../..', 'system/agents/qa-judge', 'agent.md'));
    });

    it('28 批次 1:曾经因同名冲突恒返回 null 的 id 现在正常命中', () => {
      // evolver-youtube-crawler 过去同时存在于 system/agents/ 与 system/templates/,
      // 模板那份从未入库,于是这里的 source_kind='template' 过滤让它恒返回 null,
      // hire() 每次都退回全量拷贝。目录合并后只剩一份本体,过滤也去掉了。
      insertSystemAgent({ id: 'evolver-youtube-crawler' });

      const result = graphService.lookupSharedAgentMd('evolver-youtube-crawler');

      expect(result).toBe(
        path.resolve(__dirname, '../../..', 'system/agents/evolver-youtube-crawler', 'agent.md'),
      );
      expect(fs.existsSync(result)).toBe(true);
    });

    it('本体在库但磁盘上没有 agent.md → null(ceo 那种残缺本体的历史教训)', () => {
      insertSystemAgent({ id: 'no-such-body-on-disk' });

      expect(graphService.lookupSharedAgentMd('no-such-body-on-disk')).toBeNull();
    });

    it('returns null when the id does not exist at all', () => {
      const result = graphService.lookupSharedAgentMd('does-not-exist');
      expect(result).toBeNull();
    });
  });

  describe('upsertOrgGraphNode', () => {
    it('creates an org_graph_nodes row when system_agents already has the id', () => {
      insertSystemAgent({ id: 'qa-judge' });

      graphService.upsertOrgGraphNode('acme-co', 'qa-judge', 'qa-judge');

      const db = graphDb.getDb();
      const node = db.prepare('SELECT * FROM org_graph_nodes WHERE company_id = ? AND role_name = ?').get('acme-co', 'qa-judge');
      expect(node).toBeTruthy();
      expect(node.system_agent_id).toBe('qa-judge');

      // Must not have touched/duplicated the existing system_agents row.
      const agentCount = db.prepare('SELECT COUNT(*) AS c FROM system_agents WHERE id = ?').get('qa-judge').c;
      expect(agentCount).toBe(1);
    });

    it('inserts a fallback origin=created system_agents row when the id is missing', () => {
      graphService.upsertOrgGraphNode('acme-co', 'custom-role', 'custom-role');

      const db = graphDb.getDb();
      const agent = db.prepare('SELECT * FROM system_agents WHERE id = ?').get('custom-role');
      expect(agent).toBeTruthy();
      expect(agent.scope).toBe('company');
      expect(agent.owner_company_id).toBe('acme-co');
      expect(agent.origin).toBe('created');
      // 28 批次 4:本体恒在 system/agents/{id},公司侧没有第二个落点可指
      expect(agent.legacy_path).toBe('system/agents/custom-role');
      expect(agent.status).toBe('active');
      expect(agent.benchmark_score).toBeNull();

      const node = db.prepare('SELECT * FROM org_graph_nodes WHERE company_id = ? AND role_name = ?').get('acme-co', 'custom-role');
      expect(node).toBeTruthy();
      expect(node.system_agent_id).toBe('custom-role');
    });

    it('is idempotent: calling twice for the same (companyId, roleName) does not duplicate org_graph_nodes rows', () => {
      insertSystemAgent({ id: 'qa-judge' });

      graphService.upsertOrgGraphNode('acme-co', 'qa-judge', 'qa-judge');
      graphService.upsertOrgGraphNode('acme-co', 'qa-judge', 'qa-judge');

      const db = graphDb.getDb();
      const count = db.prepare('SELECT COUNT(*) AS c FROM org_graph_nodes WHERE company_id = ? AND role_name = ?').get('acme-co', 'qa-judge').c;
      expect(count).toBe(1);
    });
  });
});