import { describe, it, before, after } from 'node:test' import assert from 'node:assert/strict' import fs from 'node:fs/promises' import path from 'node:path' import os from 'node:os' import { findMarkdownFiles, getNote, searchNotes, vaultContext, } from './vault.js' let tmpDir before(async () => { tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'laputa-mcp-test-')) await fs.mkdir(path.join(tmpDir, 'project'), { recursive: true }) await fs.mkdir(path.join(tmpDir, 'note'), { recursive: true }) await fs.writeFile(path.join(tmpDir, 'project', 'test-project.md'), `--- title: Test Project is_a: Project status: Active --- # Test Project This is a test project for the MCP server. `) await fs.writeFile(path.join(tmpDir, 'note', 'daily-log.md'), `--- title: Daily Log is_a: Note --- # Daily Log Today I worked on the MCP server implementation. `) await fs.writeFile(path.join(tmpDir, 'project', 'second-project.md'), `--- title: Second Project type: Project status: Draft belongs_to: - "[[project/test-project]]" --- # Second Project Another project for testing list and context. `) }) after(async () => { await fs.rm(tmpDir, { recursive: true, force: true }) }) describe('findMarkdownFiles', () => { it('should find all .md files recursively', async () => { const files = await findMarkdownFiles(tmpDir) assert.equal(files.length, 3) assert.ok(files.some(f => f.endsWith('test-project.md'))) assert.ok(files.some(f => f.endsWith('daily-log.md'))) assert.ok(files.some(f => f.endsWith('second-project.md'))) }) }) describe('getNote', () => { it('should read a note with parsed frontmatter', async () => { const note = await getNote(tmpDir, 'project/test-project.md') assert.equal(note.path, 'project/test-project.md') assert.equal(note.frontmatter.title, 'Test Project') assert.equal(note.frontmatter.is_a, 'Project') assert.ok(note.content.includes('test project for the MCP server')) }) it('should throw for missing notes', async () => { await assert.rejects( () => getNote(tmpDir, 'nonexistent.md'), { code: 'ENOENT' } ) }) }) describe('searchNotes', () => { it('should find notes matching title', async () => { const results = await searchNotes(tmpDir, 'Test Project') assert.ok(results.length >= 1) assert.equal(results[0].title, 'Test Project') }) it('should find notes matching content', async () => { const results = await searchNotes(tmpDir, 'MCP server') assert.ok(results.length >= 1) }) it('should return empty for no matches', async () => { const results = await searchNotes(tmpDir, 'xyzzy-nonexistent-12345') assert.equal(results.length, 0) }) it('should respect limit', async () => { const results = await searchNotes(tmpDir, 'project', 1) assert.ok(results.length <= 1) }) }) describe('vaultContext', () => { it('should return types, recent notes, and vault path', async () => { const ctx = await vaultContext(tmpDir) assert.ok(Array.isArray(ctx.types)) assert.ok(Array.isArray(ctx.recentNotes)) assert.equal(ctx.vaultPath, tmpDir) }) it('should include known entity types', async () => { const ctx = await vaultContext(tmpDir) assert.ok(ctx.types.includes('Project')) assert.ok(ctx.types.includes('Note')) }) it('should cap recent notes at 20', async () => { const ctx = await vaultContext(tmpDir) assert.ok(ctx.recentNotes.length <= 20) }) it('should include path and title in recent notes', async () => { const ctx = await vaultContext(tmpDir) for (const note of ctx.recentNotes) { assert.ok(note.path) assert.ok(note.title) } }) it('should include folders', async () => { const ctx = await vaultContext(tmpDir) assert.ok(ctx.folders.includes('project/')) assert.ok(ctx.folders.includes('note/')) }) it('should report correct note count', async () => { const ctx = await vaultContext(tmpDir) assert.equal(ctx.noteCount, 3) }) })