test: add real filesystem vault integration tests

Replace mock vault approach with real filesystem I/O for integration
tests. Each test copies tests/fixtures/test-vault/ to a temp directory,
overrides mock handlers to point at it, and verifies actual file
operations through the vite dev server middleware.

Tests cover: vault loading, archive/trash filtering, note creation,
rename with filesystem update, wikilink cascade on rename, relationship
display, and real file content loading.

Also extends vite.config.ts vault API middleware with write endpoints
(save, rename, delete, search, entry) and fixes getBool to handle
YAML 1.2 string values like "Yes"/"yes" (js-yaml 4.x compatibility).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-03-11 20:46:45 +01:00
parent 891e298f3a
commit 5107cd13f4
14 changed files with 517 additions and 16 deletions

View File

@@ -1,7 +1,7 @@
/**
* Vault API detection and proxy for browser dev mode.
* When a local vault API server is running, routes read commands through it
* instead of returning hardcoded mock data.
* When a local vault API server is running, routes read and write commands
* through it instead of returning hardcoded mock data.
*/
let vaultApiAvailable: boolean | null = null
@@ -18,25 +18,60 @@ async function checkVaultApi(): Promise<boolean> {
return vaultApiAvailable
}
const VAULT_API_COMMANDS: Record<string, (args: Record<string, unknown>) => string | null> = {
list_vault: (args) => args.path ? `/api/vault/list?path=${encodeURIComponent(args.path as string)}` : null,
reload_vault: (args) => args.path ? `/api/vault/list?path=${encodeURIComponent(args.path as string)}&reload=1` : null,
get_note_content: (args) => args.path ? `/api/vault/content?path=${encodeURIComponent(args.path as string)}` : null,
get_all_content: (args) => args.path ? `/api/vault/all-content?path=${encodeURIComponent(args.path as string)}` : null,
interface VaultApiRequest {
url: string
method?: string
body?: unknown
}
/** Tracks last vault path for commands that don't receive it as an argument. */
let lastVaultPath: string | null = null
const VAULT_API_COMMANDS: Record<string, (args: Record<string, unknown>) => VaultApiRequest | null> = {
list_vault: (args) => {
if (args.path) lastVaultPath = args.path as string
return args.path ? { url: `/api/vault/list?path=${encodeURIComponent(args.path as string)}` } : null
},
reload_vault: (args) => {
if (args.path) lastVaultPath = args.path as string
return args.path ? { url: `/api/vault/list?path=${encodeURIComponent(args.path as string)}&reload=1` } : null
},
reload_vault_entry: (args) =>
args.path ? { url: `/api/vault/entry?path=${encodeURIComponent(args.path as string)}` } : null,
get_note_content: (args) =>
args.path ? { url: `/api/vault/content?path=${encodeURIComponent(args.path as string)}` } : null,
get_all_content: (args) =>
args.path ? { url: `/api/vault/all-content?path=${encodeURIComponent(args.path as string)}` } : null,
save_note_content: (args) =>
args.path ? { url: '/api/vault/save', method: 'POST', body: { path: args.path, content: args.content } } : null,
rename_note: (args) =>
args.old_path ? { url: '/api/vault/rename', method: 'POST', body: { vault_path: args.vault_path, old_path: args.old_path, new_title: args.new_title } } : null,
delete_note: (args) =>
args.path ? { url: '/api/vault/delete', method: 'POST', body: { path: args.path } } : null,
search_vault: (args) => {
const q = args.query as string
if (!q || !lastVaultPath) return null
return { url: `/api/vault/search?vault_path=${encodeURIComponent(lastVaultPath)}&query=${encodeURIComponent(q)}&mode=${encodeURIComponent((args.mode as string) || 'all')}` }
},
}
export async function tryVaultApi<T>(cmd: string, args?: Record<string, unknown>): Promise<T | undefined> {
const available = await checkVaultApi()
if (!available) return undefined
const urlBuilder = VAULT_API_COMMANDS[cmd]
if (!urlBuilder || !args) return undefined
const requestBuilder = VAULT_API_COMMANDS[cmd]
if (!requestBuilder || !args) return undefined
const url = urlBuilder(args)
if (!url) return undefined
const request = requestBuilder(args)
if (!request) return undefined
try {
const res = await fetch(url)
const fetchOpts: RequestInit = { method: request.method || 'GET' }
if (request.body) {
fetchOpts.headers = { 'Content-Type': 'application/json' }
fetchOpts.body = JSON.stringify(request.body)
}
const res = await fetch(request.url, fetchOpts)
if (!res.ok) return undefined
const data = await res.json()
return (cmd === 'get_note_content' ? data.content : data) as T