Copies source (no node_modules, dist, .tasks, .wiki, __pycache__) for: - projects-meta-mcp v2.25.0 (TypeScript/Node) - wiki-graph v0.3.1 (TypeScript/Node) - interns-mcp v0.3.3 (Python/FastMCP) .gitignore: exclude lib build artefacts (node_modules, dist, .venv, __pycache__, *.pyc) bootstrap.ps1: add MCP build step — npm install+build for TS servers, venv+pip for Python Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
154 lines
5.5 KiB
TypeScript
154 lines
5.5 KiB
TypeScript
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();
|
|
});
|
|
});
|