// Support Windows du daemon : helpers purs, testés avec la plateforme INJECTÉE (ils doivent donc être // vérifiables depuis Linux). Chacun corrige un point qui rendait le daemon inutilisable sur Windows. import { describe, expect, it } from 'vitest'; import { buildSpawnSpec, resolveInteractiveShell, resolvePlainShell, whichCommand } from '../src/core/claude-launcher.js'; import { askpassScript } from '../src/core/git-auth.js'; import { defaultDataRoot } from '../src/config.js'; describe('recherche du binaire claude dans le PATH', () => { it('utilise where.exe sur Windows, which ailleurs', () => { expect(whichCommand('win32')).toEqual({ file: 'where.exe', args: ['claude'] }); expect(whichCommand('linux')).toEqual({ file: 'which', args: ['claude'] }); expect(whichCommand('darwin').file).toBe('which'); }); }); describe('shell de lancement', () => { it('POSIX : $SHELL connu en login interactif, sinon bash', () => { expect(resolveInteractiveShell('linux', { SHELL: '/usr/bin/zsh' })).toEqual({ file: '/usr/bin/zsh', args: ['-l', '-i'] }); expect(resolveInteractiveShell('linux', { SHELL: '/bin/dash' })).toEqual({ file: 'bash', args: ['-l', '-i'] }); expect(resolveInteractiveShell('linux', {})).toEqual({ file: 'bash', args: ['-l', '-i'] }); }); it('Windows : PowerShell qui reste attaché après la commande auto-tapée', () => { const shell = resolveInteractiveShell('win32', {}); expect(shell.file).toBe('powershell.exe'); expect(shell.args).toContain('-NoExit'); }); it('Windows : le shell est surchargeable par ARBORETUM_SHELL', () => { expect(resolveInteractiveShell('win32', { ARBORETUM_SHELL: 'pwsh.exe' }).file).toBe('pwsh.exe'); }); it('terminal simple : bash --norc sur POSIX, PowerShell sur Windows', () => { expect(resolvePlainShell('linux')).toEqual({ file: 'bash', args: ['--norc'] }); expect(resolvePlainShell('win32').file).toBe('powershell.exe'); }); }); describe('buildSpawnSpec · plateforme injectée', () => { it('command=bash sur Windows lance PowerShell (le contrat d’API reste claude|bash)', () => { const spec = buildSpawnSpec({ command: 'bash', platform: 'win32' }); expect(spec.file).toBe('powershell.exe'); expect(spec.env.TERM).toBe('xterm-256color'); }); it('command=bash avec login sur Windows reste attaché', () => { expect(buildSpawnSpec({ command: 'bash', login: true, platform: 'win32' }).args).toContain('-NoExit'); }); it('command=bash sur Linux inchangé', () => { expect(buildSpawnSpec({ command: 'bash', platform: 'linux' })).toMatchObject({ file: 'bash', args: ['--norc'] }); }); }); describe('script askpass git', () => { it('Windows : .cmd (un .sh à shebang n’y est pas exécutable)', () => { const s = askpassScript('win32'); expect(s.name).toBe('askpass.cmd'); expect(s.content).toContain('@echo off'); expect(s.content).toContain('ARB_GIT_USER'); expect(s.content).toContain('ARB_GIT_PASS'); }); it('POSIX : .sh avec shebang', () => { const s = askpassScript('linux'); expect(s.name).toBe('askpass.sh'); expect(s.content.startsWith('#!/bin/sh')).toBe(true); expect(s.mode).toBe(0o700); }); it('les deux variantes distinguent Username du mot de passe', () => { for (const p of ['win32', 'linux'] as const) { const c = askpassScript(p).content; expect(c).toMatch(/Username/i); } }); }); describe('racine des données', () => { it('XDG_DATA_HOME est prioritaire partout (l’app de bureau s’en sert pour isoler)', () => { expect(defaultDataRoot('win32', { XDG_DATA_HOME: '/iso' }, '/home/u')).toBe('/iso'); expect(defaultDataRoot('linux', { XDG_DATA_HOME: '/iso' }, '/home/u')).toBe('/iso'); }); it('Windows : %APPDATA%, avec repli sur AppData/Roaming', () => { expect(defaultDataRoot('win32', { APPDATA: 'C:\\Users\\u\\AppData\\Roaming' }, 'C:\\Users\\u')).toBe('C:\\Users\\u\\AppData\\Roaming'); expect(defaultDataRoot('win32', {}, '/home/u')).toBe('/home/u/AppData/Roaming'); }); it('POSIX : ~/.local/share', () => { expect(defaultDataRoot('linux', {}, '/home/u')).toBe('/home/u/.local/share'); expect(defaultDataRoot('darwin', {}, '/Users/u')).toBe('/Users/u/.local/share'); }); });