import { describe, it, expect, beforeAll } from 'vitest'; import { pool } from '../../../../lib/db/pool.js'; import { resetDb } from '../../../helpers/db.js'; import { migrateUp } from '../../../../lib/db/migrate.js'; import { searchTool } from '../../../../lib/ai/agent/tools/search.js'; import { readTool } from '../../../../lib/ai/agent/tools/read.js'; let spaceId, pageId; beforeAll(async () => { await resetDb(); await migrateUp(); ({ rows: [{ id: spaceId }] } = await pool.query( `INSERT INTO spaces(slug,name) VALUES('s','S') RETURNING id`)); // pages requires slug (NOT NULL) and body_md (not body) ({ rows: [{ id: pageId }] } = await pool.query( `INSERT INTO pages(space_id,slug,title,body_md) VALUES($1,'telemetry-export','Telemetry export','export CSV via phone') RETURNING id`, [spaceId])); }); describe('search tool', () => { it('returns FTS hits scoped to the space', async () => { const ctx = { space_id: spaceId, actor: { kind: 'user', id: null } }; const out = await searchTool.handler({ q: 'telemetry' }, ctx); expect(Array.isArray(out.results)).toBe(true); // fts() returns title_or_snippet (not title) expect(out.results.some(r => r.title_or_snippet?.includes('Telemetry'))).toBe(true); }); }); describe('read tool', () => { it('fetches a page by id', async () => { const out = await readTool.handler({ kind: 'page', id: pageId }, { space_id: spaceId }); expect(out.title).toBe('Telemetry export'); }); it('reports not-found cleanly', async () => { const out = await readTool.handler( { kind: 'page', id: '00000000-0000-0000-0000-000000000000' }, { space_id: spaceId }); expect(out.error).toMatch(/not found/i); }); });