feat(settings): réglages Claude CLI (binaire + ~/.claude) & notif « disponible »
- Réglages → Claude CLI : override du chemin du binaire `claude` (effet à la prochaine session, fallback `which claude`), diagnostic de détection, et override de la racine ~/.claude (effet au redémarrage). Validateurs stricts. - Push : notifie aussi sur le front busy→idle (session redevenue disponible). - Renomme l'état affiché idle → « disponible » / « available » (web EN/FR + VS Code) ; l'enum SessionActivity du protocole reste inchangé.
This commit is contained in:
@@ -48,6 +48,8 @@ beforeAll(() => {
|
||||
dataDir: dir,
|
||||
allowedOrigins: ['https://host.tailnet.ts.net'],
|
||||
printToken: false,
|
||||
claudeHome: join(dir, 'claude'),
|
||||
claudeHomeFromFlag: false,
|
||||
claudeProjectsDir: join(dir, 'claude', 'projects'),
|
||||
claudeSessionsDir: join(dir, 'claude', 'sessions'),
|
||||
vapidContact: 'mailto:test@localhost',
|
||||
@@ -77,6 +79,11 @@ describe('GET /api/v1/settings', () => {
|
||||
// défauts de découverte : AUCUNE racine (clean install → pas de scan) + intervalle 5 min
|
||||
expect(body.settings.scanRoots).toEqual([]);
|
||||
expect(body.settings.scanIntervalMin).toBe(5);
|
||||
// Claude CLI : aucun override par défaut + diagnostic via PATH (execFileSync mocké → /usr/bin/claude).
|
||||
expect(body.settings.claudeBinPath).toBeNull();
|
||||
expect(body.settings.claudeHome).toBeNull();
|
||||
expect(body.server.claudeHome).toBe(join(dir, 'claude'));
|
||||
expect(body.server.claudeBin).toEqual({ path: '/usr/bin/claude', source: 'path', ok: true });
|
||||
});
|
||||
|
||||
it('n’expose AUCUN secret (server_secret, clé privée VAPID)', async () => {
|
||||
@@ -137,3 +144,46 @@ describe('PATCH /api/v1/settings — découverte des dépôts', () => {
|
||||
expect(neg.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /api/v1/settings — Claude CLI', () => {
|
||||
// process.execPath (node) est un fichier exécutable réel garanti sur la machine de test.
|
||||
const realBin = process.execPath;
|
||||
|
||||
it('enregistre un chemin de binaire exécutable et reflète le diagnostic (source=configured)', async () => {
|
||||
const res = await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { claudeBinPath: realBin } });
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = res.json() as SettingsResponse;
|
||||
expect(body.settings.claudeBinPath).toBe(realBin);
|
||||
expect(body.server.claudeBin).toEqual({ path: realBin, source: 'configured', ok: true });
|
||||
});
|
||||
|
||||
it('réinitialise le chemin avec "" (retour à l’auto-détection via PATH)', async () => {
|
||||
await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { claudeBinPath: realBin } });
|
||||
const res = await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { claudeBinPath: '' } });
|
||||
const body = res.json() as SettingsResponse;
|
||||
expect(body.settings.claudeBinPath).toBeNull();
|
||||
expect(body.server.claudeBin.source).toBe('path'); // de nouveau via PATH (mock → /usr/bin/claude)
|
||||
});
|
||||
|
||||
it('rejette un chemin non absolu ou non exécutable (400)', async () => {
|
||||
for (const claudeBinPath of ['relative/claude', '/a/../b', dir /* répertoire, pas un fichier */]) {
|
||||
const res = await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { claudeBinPath } });
|
||||
expect(res.statusCode).toBe(400);
|
||||
}
|
||||
});
|
||||
|
||||
it('enregistre un override claude_home (répertoire existant) et le réinitialise avec ""', async () => {
|
||||
const set = await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { claudeHome: dir } });
|
||||
expect(set.statusCode).toBe(200);
|
||||
expect((set.json() as SettingsResponse).settings.claudeHome).toBe(dir);
|
||||
const reset = await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { claudeHome: '' } });
|
||||
expect((reset.json() as SettingsResponse).settings.claudeHome).toBeNull();
|
||||
});
|
||||
|
||||
it('rejette un claude_home non absolu ou inexistant/fichier (400)', async () => {
|
||||
for (const claudeHome of ['relative/.claude', join(dir, 'settings.db') /* fichier */, join(dir, 'nope')]) {
|
||||
const res = await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { claudeHome } });
|
||||
expect(res.statusCode).toBe(400);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user