import { describe, test, expect } from 'vitest'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; import { makeWikiGraphTools } from '../src/tools.js'; import { GraphCache } from '../src/cache.js'; const here = dirname(fileURLToPath(import.meta.url)); const FIXTURE = join(here, 'fixtures', 'wiki'); function toolMap() { const tools = makeWikiGraphTools(new GraphCache()); return new Map(tools.map((t) => [t.name, t])); } function textOf(res: { content: Array<{ type: string; text: string }>; isError?: boolean }) { return res.content.map((c) => c.text).join('\n'); } describe('wiki-graph MCP tools', () => { const tools = toolMap(); test('exposes exactly the five v1 tools', () => { expect([...tools.keys()].sort()).toEqual([ 'backlinks', 'neighbors', 'orphans', 'path', 'stats', ]); }); test('every tool requires a corpus argument', () => { for (const t of tools.values()) { expect(t.inputSchema.required).toContain('corpus'); } }); test('path: returns the chain', () => { const res = tools.get('path')!.handler({ corpus: FIXTURE, from: 'a', to: 'c' }); expect(textOf(res)).toContain('a → b → c'); }); test('path: reports absence', () => { const res = tools.get('path')!.handler({ corpus: FIXTURE, from: 'a', to: 'e' }); expect(textOf(res).toLowerCase()).toContain('no path'); }); test('neighbors: honors depth', () => { const res = tools.get('neighbors')!.handler({ corpus: FIXTURE, node: 'a', depth: 2 }); const txt = textOf(res); expect(txt).toContain('b'); expect(txt).toContain('c'); }); test('backlinks: lists incoming', () => { const res = tools.get('backlinks')!.handler({ corpus: FIXTURE, node: 'c' }); const txt = textOf(res); expect(txt).toContain('b'); expect(txt).toContain('d'); }); test('orphans: lists unlinked pages and dangling targets', () => { const res = tools.get('orphans')!.handler({ corpus: FIXTURE }); const txt = textOf(res); expect(txt).toContain('e'); expect(txt).toContain(''); }); test('stats: reports counts', () => { const res = tools.get('stats')!.handler({ corpus: FIXTURE }); const txt = textOf(res); expect(txt).toContain('5'); expect(txt).toContain('nodes'); }); test('missing corpus is a clean error, not a throw', () => { const res = tools.get('stats')!.handler({}); expect(res.isError).toBe(true); expect(textOf(res).toLowerCase()).toContain('corpus'); }); });