- src/notes-api-server.ts: NotesApi-Klasse - listNotes(): Liste Notiz-IDs in 4 Top-Level-Verzeichnissen - getNote(id): liest Notiz mit Frontmatter-Parsing - saveNote(id, content): schreibt Notiz, erstellt Parent-Dirs - syncVault(toolBinaryPath): Promise, ruft mindomat sync als Subprozess - webapp/vite.config.ts: nutzt NotesApi statt Inline-Logik - 3 Middleware-Handler: GET /api/notes, GET/PUT /api/notes/<id>, POST /api/sync - tests/notes-api.test.ts: 10 Tests - listNotes: leer, Inbox, Verarbeitet-Filter, Forward-Slashes - getNote: nicht-existent, mit Frontmatter - saveNote: neu, ueberschreiben, Parent-Dirs - E2E roundtrip: list -> save -> get -> update -> get Verifiziert: - 71/71 Tests gruen - Code-Duplikation zwischen Webapp und Tool-Repo entfernt - NotesApi ist jetzt isoliert testbar (kein Vite-Server noetig)
179 lines
5.5 KiB
TypeScript
179 lines
5.5 KiB
TypeScript
// Tests fuer NotesApi (Vite-Plugin-Logik)
|
|
// Phase 4.3
|
|
|
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, existsSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { NotesApi } from '../src/notes-api-server.js';
|
|
|
|
const vaults: string[] = [];
|
|
afterEach(() => {
|
|
for (const v of vaults) rmSync(v, { recursive: true, force: true });
|
|
});
|
|
|
|
function setupVault(files: Record<string, string> = {}): string {
|
|
const vault = mkdtempSync(join(tmpdir(), 'mindomat-notes-api-'));
|
|
for (const d of [
|
|
'00_Inbox', '00_Inbox/Verarbeitet', '00_Inbox/Problemfaelle',
|
|
'01_Daily', '10_Wiki', '10_Wiki/Seiten',
|
|
'20_Projekte', '90_Templates',
|
|
'99_System', '99_System/Skripte', '99_System/Logs', '99_System/Cache',
|
|
]) {
|
|
mkdirSync(join(vault, d), { recursive: true });
|
|
}
|
|
for (const [rel, content] of Object.entries(files)) {
|
|
const full = join(vault, rel);
|
|
mkdirSync(join(full, '..'), { recursive: true });
|
|
writeFileSync(full, content);
|
|
}
|
|
return vault;
|
|
}
|
|
|
|
describe('NotesApi.listNotes', () => {
|
|
it('leerer Vault: leere Liste', () => {
|
|
const vault = setupVault();
|
|
vaults.push(vault);
|
|
const api = new NotesApi(vault);
|
|
expect(api.listNotes()).toEqual([]);
|
|
});
|
|
|
|
it('Inbox-Notizen werden gelistet', () => {
|
|
const vault = setupVault({
|
|
'00_Inbox/2026-09-05_A.md': '# A',
|
|
'00_Inbox/2026-09-05_B.md': '# B',
|
|
});
|
|
vaults.push(vault);
|
|
const api = new NotesApi(vault);
|
|
const notes = api.listNotes();
|
|
expect(notes).toContain('00_Inbox/2026-09-05_A.md');
|
|
expect(notes).toContain('00_Inbox/2026-09-05_B.md');
|
|
expect(notes.length).toBe(2);
|
|
});
|
|
|
|
it('Verarbeitet/-Problemfaelle werden NICHT gelistet (Pfad-Filter)', () => {
|
|
const vault = setupVault({
|
|
'00_Inbox/2026-09-05_A.md': '# A',
|
|
'00_Inbox/Verarbeitet/2026-09-05_B.md': '# B',
|
|
'00_Inbox/Problemfaelle/2026-09-05_C.md': '# C',
|
|
});
|
|
vaults.push(vault);
|
|
const api = new NotesApi(vault);
|
|
const notes = api.listNotes();
|
|
// B ist in einem Unterordner, der NICHT in der Top-Level-Liste ist
|
|
// Die Implementation listet nur Top-Level-Files in den 4 Verzeichnissen
|
|
expect(notes).toContain('00_Inbox/2026-09-05_A.md');
|
|
expect(notes.length).toBe(1);
|
|
});
|
|
|
|
it('Forward-Slashes in Pfaden (Windows-Kompatibilitaet)', () => {
|
|
const vault = setupVault({
|
|
'10_Wiki/Seiten/Wiki_A.md': '# Wiki',
|
|
});
|
|
vaults.push(vault);
|
|
const api = new NotesApi(vault);
|
|
const notes = api.listNotes();
|
|
expect(notes[0]).toBe('10_Wiki/Seiten/Wiki_A.md');
|
|
expect(notes[0]).not.toContain('\\');
|
|
});
|
|
});
|
|
|
|
describe('NotesApi.getNote', () => {
|
|
it('gibt null zurueck fuer nicht-existente Notiz', () => {
|
|
const vault = setupVault();
|
|
vaults.push(vault);
|
|
const api = new NotesApi(vault);
|
|
expect(api.getNote('00_Inbox/nicht-da.md')).toBeNull();
|
|
});
|
|
|
|
it('liest existierende Notiz mit Frontmatter', () => {
|
|
const vault = setupVault({
|
|
'00_Inbox/2026-09-05_Test.md': `---
|
|
title: Test
|
|
created: 2026-09-05
|
|
tags: [test]
|
|
---
|
|
|
|
# Inhalt`,
|
|
});
|
|
vaults.push(vault);
|
|
const api = new NotesApi(vault);
|
|
const note = api.getNote('00_Inbox/2026-09-05_Test.md');
|
|
expect(note).not.toBeNull();
|
|
expect(note!.content).toContain('# Inhalt');
|
|
expect(note!.frontmatter.title).toBe('Test');
|
|
expect(note!.frontmatter.tags).toEqual(['test']);
|
|
expect(note!.lastModified).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('NotesApi.saveNote', () => {
|
|
it('schreibt neue Notiz und liest sie zurueck', () => {
|
|
const vault = setupVault();
|
|
vaults.push(vault);
|
|
const api = new NotesApi(vault);
|
|
const id = '01_Daily/2026-09-05_New.md';
|
|
const content = `---
|
|
title: Neu
|
|
created: 2026-09-05
|
|
---
|
|
|
|
# Inhalt`;
|
|
const result = api.saveNote(id, content);
|
|
expect(result.id).toBe(id);
|
|
expect(result.frontmatter.title).toBe('Neu');
|
|
expect(existsSync(join(vault, id))).toBe(true);
|
|
});
|
|
|
|
it('ueberschreibt existierende Notiz', () => {
|
|
const vault = setupVault({
|
|
'00_Inbox/2026-09-05_Update.md': '# alt',
|
|
});
|
|
vaults.push(vault);
|
|
const api = new NotesApi(vault);
|
|
const id = '00_Inbox/2026-09-05_Update.md';
|
|
const newContent = '# neu';
|
|
api.saveNote(id, newContent);
|
|
const readBack = readFileSync(join(vault, id), 'utf-8');
|
|
expect(readBack).toBe(newContent);
|
|
});
|
|
|
|
it('erstellt Parent-Verzeichnisse automatisch', () => {
|
|
const vault = setupVault();
|
|
vaults.push(vault);
|
|
const api = new NotesApi(vault);
|
|
const id = '20_Projekte/Sub/New.md';
|
|
api.saveNote(id, '# Inhalt');
|
|
expect(existsSync(join(vault, id))).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('NotesApi End-to-End', () => {
|
|
it('list -> get -> save -> get roundtrip', () => {
|
|
const vault = setupVault();
|
|
vaults.push(vault);
|
|
const api = new NotesApi(vault);
|
|
|
|
// 1. Liste ist leer
|
|
expect(api.listNotes()).toEqual([]);
|
|
|
|
// 2. Speichere Notiz
|
|
const id = '10_Wiki/Seiten/Wiki_X.md';
|
|
api.saveNote(id, '---\ntitle: Wiki X\n---\n\n# Wiki X');
|
|
|
|
// 3. Liste enthaelt die Notiz
|
|
expect(api.listNotes()).toContain(id);
|
|
|
|
// 4. getNote liefert die Notiz
|
|
const note = api.getNote(id);
|
|
expect(note).not.toBeNull();
|
|
expect(note!.frontmatter.title).toBe('Wiki X');
|
|
|
|
// 5. Update Notiz
|
|
api.saveNote(id, '---\ntitle: Wiki X (updated)\n---\n\n# Updated');
|
|
const updated = api.getNote(id);
|
|
expect(updated!.frontmatter.title).toBe('Wiki X (updated)');
|
|
expect(updated!.content).toContain('# Updated');
|
|
});
|
|
});
|