// @vitest-environment node import { beforeEach, describe, expect, it, vi } from 'vitest'; const execCliMock = vi.fn(); const resolveBinaryMock = vi.fn(); vi.mock('@main/utils/childProcess', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, execCli: (...args: Parameters) => execCliMock(...args), }; }); vi.mock('@main/services/team/ClaudeBinaryResolver', () => ({ ClaudeBinaryResolver: { resolve: () => resolveBinaryMock(), clearCache: vi.fn(), }, })); vi.mock('@main/utils/shellEnv', () => ({ resolveInteractiveShellEnv: vi.fn(() => Promise.resolve({})), resolveInteractiveShellEnvBestEffort: vi.fn(() => Promise.resolve({})), getCachedShellEnv: vi.fn(() => null), getShellPreferredHome: vi.fn(() => '/Users/tester'), })); vi.mock('@main/utils/cliPathMerge', () => ({ buildMergedCliPath: vi.fn(() => '/usr/local/bin:/usr/bin'), })); vi.mock('@main/utils/cliAuthDiagLog', () => ({ appendCliAuthDiag: vi.fn(() => Promise.resolve(undefined)), })); vi.mock('https', () => ({ default: { get: vi.fn(() => { const req = { setTimeout: vi.fn(), on: vi.fn((event: string, handler: (error: Error) => void) => { if (event === 'error') { queueMicrotask(() => handler(new Error('offline'))); } return req; }), destroy: vi.fn(), }; return req; }), }, })); vi.mock('http', () => ({ default: { get: vi.fn(() => { const req = { setTimeout: vi.fn(), on: vi.fn((event: string, handler: (error: Error) => void) => { if (event === 'error') { queueMicrotask(() => handler(new Error('offline'))); } return req; }), destroy: vi.fn(), }; return req; }), }, })); import { CliInstallerService } from '@main/services/infrastructure/CliInstallerService'; describe('CliInstallerService health check', () => { let service: CliInstallerService; beforeEach(() => { vi.clearAllMocks(); vi.spyOn(console, 'warn').mockImplementation(() => undefined); vi.spyOn(console, 'error').mockImplementation(() => undefined); service = new CliInstallerService(); }); it('does not treat a found binary as installed until --version succeeds', async () => { resolveBinaryMock.mockResolvedValue('/usr/local/bin/claude'); execCliMock.mockRejectedValue(new Error('spawn EACCES')); const status = await service.getStatus(); expect(status.installed).toBe(false); expect(status.binaryPath).toBe('/usr/local/bin/claude'); expect(status.installedVersion).toBeNull(); expect(status.launchError).toContain('spawn EACCES'); expect(status.authStatusChecking).toBe(false); }); it('marks the CLI installed after a successful version probe', async () => { resolveBinaryMock.mockResolvedValue('/usr/local/bin/claude'); execCliMock .mockResolvedValueOnce({ stdout: '2.1.100 (Claude Code)', stderr: '' }) .mockResolvedValueOnce({ stdout: '{"loggedIn":true,"authMethod":"oauth_token"}', stderr: '', }); const status = await service.getStatus(); expect(status.installed).toBe(true); expect(status.binaryPath).toBe('/usr/local/bin/claude'); expect(status.installedVersion).toBe('2.1.100'); expect(status.launchError).toBeNull(); }); });