Files
factory/lib/projects-meta-mcp/tests/tools/meta.test.ts
vitya ca669d96e1 feat(lib): bundle MCP servers from .common/lib into factory/lib/
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>
2026-06-11 13:17:29 +03:00

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();
});
});