import { describe, it, expect, beforeEach } from 'vitest'; import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { writeCache } from '../../src/lib/cache.js'; import { makeMetaTools } from '../../src/tools/meta.js'; import type { Paths } from '../../src/lib/config.js'; function mkPaths(home: string): Paths { const cacheDir = join(home, '.cache'); return { cacheDir, cacheFile: join(cacheDir, 'tasks.json'), syncLog: join(cacheDir, 'sync.log'), authFile: join(home, '.config', 'auth.toml'), sharedWikiClone: join(home, 'wiki'), agendaTasksDir: join(home, 'tasks'), }; } describe('meta.status', () => { let paths: Paths; beforeEach(async () => { const home = await mkdtemp(join(tmpdir(), 'meta-')); paths = mkPaths(home); await mkdir(paths.sharedWikiClone, { recursive: true }); await mkdir(join(paths.sharedWikiClone, 'node'), { recursive: true }); await writeFile( join(paths.sharedWikiClone, 'node', 'one.md'), '---\ntitle: t\ndomain: node\n---\n\nbody', ); }); it('reports cache_missing when no cache', async () => { const tools = makeMetaTools({ paths }); const r = await (tools.find((t) => t.name === 'meta.status')!.handler({})); const parsed = JSON.parse(r.content[0].text); expect(parsed.cache_missing).toBe(true); expect(parsed.wiki_pages_count).toBe(1); expect(parsed.agenda_present).toBe(false); expect(parsed.agenda_tasks_dir).toBe(paths.agendaTasksDir); }); it('reports age and stale=false for fresh cache', async () => { await writeCache(paths.cacheFile, { schemaVersion: 3, synced_at: new Date().toISOString(), synced_from: 'https://g', machine: 'm', projects: [], errors: [], }); const tools = makeMetaTools({ paths }); const r = await (tools.find((t) => t.name === 'meta.status')!.handler({})); const parsed = JSON.parse(r.content[0].text); expect(parsed.stale).toBe(false); expect(parsed.age_seconds).toBeGreaterThanOrEqual(0); }); it('reports stale=true for >24h cache', async () => { const old = new Date(Date.now() - 25 * 3600 * 1000).toISOString(); await writeCache(paths.cacheFile, { schemaVersion: 3, synced_at: old, synced_from: 'https://g', machine: 'm', projects: [{ name: 'p', default_branch: 'main', fetched_at: old, active_tasks: [], all_tasks_count: 0, raw: '' }], errors: [{ project: 'q', reason: 'x' }], }); const tools = makeMetaTools({ paths }); const r = await (tools.find((t) => t.name === 'meta.status')!.handler({})); const parsed = JSON.parse(r.content[0].text); expect(parsed.stale).toBe(true); expect(parsed.projects_count).toBe(1); expect(parsed.errors_count).toBe(1); }); it('exposes agenda-project counts and source=local when agenda default_branch is "local"', async () => { await writeCache(paths.cacheFile, { schemaVersion: 3, synced_at: new Date().toISOString(), synced_from: 'https://g', machine: 'm', projects: [ { name: 'p', default_branch: 'main', fetched_at: new Date().toISOString(), active_tasks: [], all_tasks_count: 0, raw: '' }, { name: 'agenda', default_branch: 'local', fetched_at: new Date().toISOString(), active_tasks: [ { slug: 'm1', status: 'active', next: 'do x' }, { slug: 'm2', status: 'paused', next: 'do y' }, ], all_tasks_count: 3, raw: '...', }, ], errors: [], }); const tools = makeMetaTools({ paths }); const r = await (tools.find((t) => t.name === 'meta.status')!.handler({})); const parsed = JSON.parse(r.content[0].text); expect(parsed.agenda_present).toBe(true); expect(parsed.agenda_tasks_active).toBe(2); expect(parsed.agenda_tasks_total).toBe(3); expect(parsed.agenda_source).toBe('local'); expect(parsed.agenda_branch).toBe('local'); }); it('reports agenda_source=gitea when agenda default_branch is a real branch', async () => { await writeCache(paths.cacheFile, { schemaVersion: 3, synced_at: new Date().toISOString(), synced_from: 'https://g', machine: 'm', projects: [ { name: 'agenda', default_branch: 'main', fetched_at: new Date().toISOString(), active_tasks: [{ slug: 'g1', status: 'active', next: 'x' }], all_tasks_count: 1, raw: '...', }, ], errors: [], }); const tools = makeMetaTools({ paths }); const r = await (tools.find((t) => t.name === 'meta.status')!.handler({})); const parsed = JSON.parse(r.content[0].text); expect(parsed.agenda_present).toBe(true); expect(parsed.agenda_source).toBe('gitea'); expect(parsed.agenda_branch).toBe('main'); }); it('reports agenda_source=none when agenda absent', async () => { await writeCache(paths.cacheFile, { schemaVersion: 3, synced_at: new Date().toISOString(), synced_from: 'https://g', machine: 'm', projects: [{ name: 'p', default_branch: 'main', fetched_at: new Date().toISOString(), active_tasks: [], all_tasks_count: 0, raw: '' }], errors: [], }); const tools = makeMetaTools({ paths }); const r = await (tools.find((t) => t.name === 'meta.status')!.handler({})); const parsed = JSON.parse(r.content[0].text); expect(parsed.agenda_present).toBe(false); expect(parsed.agenda_source).toBe('none'); expect(parsed.agenda_branch).toBeNull(); }); });