Files
factory/lib/projects-meta-mcp/tests/lib/wiki-writer.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

133 lines
5.1 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
formatPage,
pagePath,
indexPath,
logPath,
rawPath,
insertIndexEntry,
appendLogEntry,
freshIndexMd,
freshLogMd,
} from '../../src/lib/wiki-writer.js';
describe('formatPage', () => {
it('produces frontmatter+body via gray-matter', () => {
const out = formatPage({
type: 'concepts',
slug: 'tasks-routing-rule',
body: 'Read local `.tasks/STATUS.md` for current project.',
frontmatter: { tags: ['routing'], domain: 'projects-meta' },
});
expect(out.startsWith('---\n')).toBe(true);
expect(out).toContain("title: tasks-routing-rule");
expect(out).toContain('type: concept');
expect(out).toContain('domain: projects-meta');
expect(out).toContain('Read local `.tasks/STATUS.md`');
});
it('auto-injects ingested_at / ingested_by / source_project', () => {
const out = formatPage({
type: 'sources',
slug: 's',
body: 'b',
ingestedAtIso: '2026-04-29T12:00:00Z',
ingestedBy: 'vitya@laptop',
sourceProject: 'modules-db',
});
expect(out).toContain("ingested_at: '2026-04-29T12:00:00Z'");
expect(out).toContain('ingested_by: vitya@laptop');
expect(out).toContain('source_project: modules-db');
});
it('respects user-supplied title and type override', () => {
const out = formatPage({
type: 'concepts',
slug: 's',
body: 'b',
frontmatter: { title: 'Hand title', type: 'design-decision' },
});
expect(out).toContain('title: Hand title');
expect(out).toContain('type: design-decision');
});
});
describe('pagePath', () => {
it('builds .wiki/<type>/<slug>.md for project wikis (default / isAgenda=false)', () => {
expect(pagePath('concepts', 'foo')).toBe('.wiki/concepts/foo.md');
expect(pagePath('raw', 'pdf-note')).toBe('.wiki/raw/pdf-note.md');
expect(pagePath('concepts', 'foo', false)).toBe('.wiki/concepts/foo.md');
});
it('builds <type>/<slug>.md for the shared wiki (isAgenda=true, no .wiki/ prefix)', () => {
expect(pagePath('concepts', 'foo', true)).toBe('concepts/foo.md');
expect(pagePath('sources', 'bar', true)).toBe('sources/bar.md');
expect(pagePath('raw', 'pdf', true)).toBe('raw/pdf.md');
});
});
describe('indexPath / logPath / rawPath', () => {
it('project wiki layout — .wiki/ prefix', () => {
expect(indexPath()).toBe('.wiki/index.md');
expect(indexPath(false)).toBe('.wiki/index.md');
expect(logPath()).toBe('.wiki/log.md');
expect(logPath(false)).toBe('.wiki/log.md');
expect(rawPath('foo')).toBe('.wiki/raw/foo.md');
expect(rawPath('foo', false)).toBe('.wiki/raw/foo.md');
});
it('shared wiki layout — repo root, no prefix', () => {
expect(indexPath(true)).toBe('index.md');
expect(logPath(true)).toBe('log.md');
expect(rawPath('foo', true)).toBe('raw/foo.md');
});
});
describe('insertIndexEntry', () => {
it('inserts entry into existing section, removes (none yet) placeholder', () => {
const before = freshIndexMd();
const after = insertIndexEntry(before, 'concepts', 'tasks-routing-rule', 'Tasks Routing Rule');
expect(after).toContain('- [tasks-routing-rule](concepts/tasks-routing-rule.md) — Tasks Routing Rule');
// Placeholder gone for Concepts section, but other sections still empty
const conceptsSection = after.slice(after.indexOf('## Concepts'), after.indexOf('## Packages'));
expect(conceptsSection).not.toContain('(none yet)');
const packagesSection = after.slice(after.indexOf('## Packages'), after.indexOf('## Sources'));
expect(packagesSection).toContain('(none yet)');
});
it('appends to non-empty section preserving prior entries', () => {
const before = `# Wiki Index\n\n## Concepts\n\n- [first](concepts/first.md) — first\n\n## Packages\n\n<!-- (none yet) -->\n`;
const after = insertIndexEntry(before, 'concepts', 'second', 'Second');
expect(after).toContain('- [first](concepts/first.md) — first');
expect(after).toContain('- [second](concepts/second.md) — Second');
});
it('skips if entry with the same slug already present', () => {
const before = `# Wiki Index\n\n## Concepts\n\n- [foo](concepts/foo.md) — Foo\n\n## Packages\n\n`;
const after = insertIndexEntry(before, 'concepts', 'foo', 'Foo Again');
expect(after).toBe(before);
});
it('appends section if missing', () => {
const before = `# Wiki Index\n\n## Overview\n\n- [overview.md](overview.md) — overview\n`;
const after = insertIndexEntry(before, 'sources', 's1', 'Source One');
expect(after).toContain('## Sources');
expect(after).toContain('- [s1](sources/s1.md) — Source One');
});
});
describe('appendLogEntry', () => {
it('appends to existing log', () => {
const before = freshLogMd();
const after = appendLogEntry(before, '2026-04-29', 'ingest', 'concepts/x');
expect(after).toContain('## [2026-04-29] ingest | concepts/x');
expect(after).toContain('# Wiki Log'); // header preserved
});
it('initializes when log is empty', () => {
const after = appendLogEntry('', '2026-04-29', 'init', 'bootstrap');
expect(after).toMatch(/^# Wiki Log/);
expect(after).toContain('## [2026-04-29] init | bootstrap');
});
});