Files
Mind-o-Mat/tests/notes-api.test.ts
T
Jannik 1590b633e5
CI / test (push) Canceled after 0s
CI / webapp (push) Canceled after 0s
v2.0.4: NotesApi-Server-Modul + 10 Tests (Phase 4.3)
- 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)
2026-09-05 12:35:35 +02:00

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