// Réglages CLI Claude (chemin du binaire + override ~/.claude) : validateurs + lecture/écriture DB. import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { openDb, setSetting, type Db } from '../src/db/index.js'; import { CLAUDE_BIN_PATH_KEY, CLAUDE_HOME_KEY, normalizeClaudeBinPath, normalizeClaudeHome, readClaudeBinPath, readClaudeHome, } from '../src/core/claude-settings.js'; let dir: string; let execFile: string; let plainFile: string; let subDir: string; beforeAll(() => { dir = mkdtempSync(join(tmpdir(), 'arboretum-claude-settings-')); execFile = join(dir, 'claude'); writeFileSync(execFile, '#!/bin/sh\n'); chmodSync(execFile, 0o755); // exécutable plainFile = join(dir, 'notexec'); writeFileSync(plainFile, 'x'); chmodSync(plainFile, 0o644); // non exécutable subDir = join(dir, 'home'); mkdirSync(subDir); }); afterAll(() => rmSync(dir, { recursive: true, force: true })); describe('normalizeClaudeBinPath', () => { it('accepte un chemin absolu vers un fichier exécutable', () => { expect(normalizeClaudeBinPath(execFile)).toBe(execFile); }); it('"" = réinitialisation (auto-détection)', () => { expect(normalizeClaudeBinPath('')).toBe(''); expect(normalizeClaudeBinPath(' ')).toBe(''); }); it('rejette un fichier non exécutable, un répertoire, un chemin relatif ou avec ".." (null)', () => { expect(normalizeClaudeBinPath(plainFile)).toBeNull(); expect(normalizeClaudeBinPath(subDir)).toBeNull(); // répertoire, pas un fichier expect(normalizeClaudeBinPath('relative/claude')).toBeNull(); expect(normalizeClaudeBinPath(join(dir, '..', 'x'))).toBeNull(); expect(normalizeClaudeBinPath(join(dir, 'missing'))).toBeNull(); expect(normalizeClaudeBinPath(42)).toBeNull(); }); }); describe('normalizeClaudeHome', () => { it('accepte un chemin absolu vers un répertoire existant', () => { expect(normalizeClaudeHome(subDir)).toBe(subDir); }); it('"" = réinitialisation (défaut)', () => { expect(normalizeClaudeHome('')).toBe(''); }); it('rejette un fichier, un chemin relatif/inexistant (null)', () => { expect(normalizeClaudeHome(execFile)).toBeNull(); // fichier, pas un répertoire expect(normalizeClaudeHome('relative/.claude')).toBeNull(); expect(normalizeClaudeHome(join(dir, 'missing'))).toBeNull(); expect(normalizeClaudeHome(null)).toBeNull(); }); }); describe('readClaudeBinPath / readClaudeHome', () => { let db: Db; beforeAll(() => { db = openDb(join(dir, 'settings.db')); }); afterAll(() => db.close()); it('null quand non défini', () => { expect(readClaudeBinPath(db)).toBeNull(); expect(readClaudeHome(db)).toBeNull(); }); it('lit la valeur stockée et traite "" stockée comme null', () => { setSetting(db, CLAUDE_BIN_PATH_KEY, execFile); setSetting(db, CLAUDE_HOME_KEY, subDir); expect(readClaudeBinPath(db)).toBe(execFile); expect(readClaudeHome(db)).toBe(subDir); setSetting(db, CLAUDE_BIN_PATH_KEY, ''); // réinitialisé setSetting(db, CLAUDE_HOME_KEY, ''); expect(readClaudeBinPath(db)).toBeNull(); expect(readClaudeHome(db)).toBeNull(); }); });