graph-service.test.js
5.45 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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
* 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, source_kind, legacy_path, 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, legacy_path, status, benchmark_score, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, 'active', NULL, ?, ?)
`).run(id, id, scope, owner_company_id, source_kind, legacy_path, now, now);
}
describe('lookupSharedAgentMd', () => {
it('returns the resolved agent.md path when a template row exists', () => {
insertSystemAgent({ id: 'qa-judge', source_kind: 'template', legacy_path: 'system/templates/qa-judge' });
const result = graphService.lookupSharedAgentMd('qa-judge');
expect(result).toBe(path.resolve(__dirname, '../../..', 'system/templates/qa-judge', 'agent.md'));
});
it('returns null when the only matching row is source_kind=global_agent (naming-collision regression)', () => {
// Regression case: evolver-youtube-crawler exists under BOTH system/agents/ and
// system/templates/ but the template row got skipped by seedTemplates() because
// seedGlobalAgents() claimed the id first. Only a global_agent row exists here —
// lookupSharedAgentMd must NOT fall back to it, or idearun's already-hired copy
// (which was physically copied from system/templates/) would silently start
// reading a different file it was never hired with.
insertSystemAgent({
id: 'evolver-youtube-crawler',
source_kind: 'global_agent',
legacy_path: 'system/agents/evolver-youtube-crawler',
});
const result = graphService.lookupSharedAgentMd('evolver-youtube-crawler');
expect(result).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', source_kind: 'template', legacy_path: 'system/templates/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 company_hired 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.source_kind).toBe('company_hired');
expect(agent.legacy_path).toBe('data/companies/acme-co/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', source_kind: 'template', legacy_path: 'system/templates/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);
});
});
});