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

129 lines
4.7 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { mkdtemp, readFile, stat, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
writeCache,
readCache,
detectCacheInvalidation,
CACHE_SCHEMA_VERSION,
type CacheFile,
} from '../../src/lib/cache.js';
const sample: CacheFile = {
schemaVersion: 3,
synced_at: '2026-04-29T12:00:00.000Z',
synced_from: 'https://git.kzntsv.site',
machine: 'test',
projects: [],
errors: [],
};
describe('cache', () => {
it('writes JSON atomically and reads it back', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const file = join(dir, 'tasks.json');
await writeCache(file, sample);
const back = await readCache(file);
expect(back).toEqual(sample);
});
it('does not leave .tmp file on success', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const file = join(dir, 'tasks.json');
await writeCache(file, sample);
await expect(stat(file + '.tmp')).rejects.toThrow();
});
it('returns null when cache file missing', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const back = await readCache(join(dir, 'missing.json'));
expect(back).toBeNull();
});
it('creates parent directory if missing', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const file = join(dir, 'nested', 'deep', 'tasks.json');
await writeCache(file, sample);
const txt = await readFile(file, 'utf8');
expect(JSON.parse(txt)).toEqual(sample);
});
it('exports CACHE_SCHEMA_VERSION = 3', () => {
expect(CACHE_SCHEMA_VERSION).toBe(3);
});
it('returns null on legacy cache without schemaVersion', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const file = join(dir, 'tasks.json');
const legacy = {
synced_at: '2026-04-29T12:00:00.000Z',
synced_from: 'https://g',
machine: 'test',
projects: [{ name: 'bare-name', default_branch: 'main', fetched_at: '2026-04-29T12:00:00Z', active_tasks: [], all_tasks_count: 0, raw: '' }],
errors: [],
};
await writeFile(file, JSON.stringify(legacy), 'utf8');
const back = await readCache(file);
expect(back).toBeNull();
});
it('returns null on schemaVersion mismatch', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const file = join(dir, 'tasks.json');
const v1 = { ...sample, schemaVersion: 1 };
await writeFile(file, JSON.stringify(v1), 'utf8');
const back = await readCache(file);
expect(back).toBeNull();
});
it('returns null on v2 cache (pre-agenda-rename, name === "_meta" entries are stale)', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const file = join(dir, 'tasks.json');
const v2 = { ...sample, schemaVersion: 2 };
await writeFile(file, JSON.stringify(v2), 'utf8');
const back = await readCache(file);
expect(back).toBeNull();
});
});
describe('detectCacheInvalidation', () => {
it('returns invalidated:false when file missing', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const r = await detectCacheInvalidation(join(dir, 'missing.json'));
expect(r).toEqual({ invalidated: false, oldVersion: null });
});
it('returns invalidated:true, oldVersion:null on legacy (no field)', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const file = join(dir, 'tasks.json');
await writeFile(file, JSON.stringify({ synced_at: 't', projects: [] }), 'utf8');
const r = await detectCacheInvalidation(file);
expect(r).toEqual({ invalidated: true, oldVersion: null });
});
it('returns invalidated:true, oldVersion:1 on v1 cache', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const file = join(dir, 'tasks.json');
await writeFile(file, JSON.stringify({ schemaVersion: 1, projects: [] }), 'utf8');
const r = await detectCacheInvalidation(file);
expect(r).toEqual({ invalidated: true, oldVersion: 1 });
});
it('returns invalidated:true, oldVersion:2 on v2 cache (pre-agenda-rename)', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const file = join(dir, 'tasks.json');
await writeFile(file, JSON.stringify({ schemaVersion: 2, projects: [] }), 'utf8');
const r = await detectCacheInvalidation(file);
expect(r).toEqual({ invalidated: true, oldVersion: 2 });
});
it('returns invalidated:false, oldVersion:3 on current cache', async () => {
const dir = await mkdtemp(join(tmpdir(), 'cache-'));
const file = join(dir, 'tasks.json');
await writeCache(file, sample);
const r = await detectCacheInvalidation(file);
expect(r).toEqual({ invalidated: false, oldVersion: 3 });
});
});