import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { listServers, printServerList, installServer, installAllServers, showServerInfo, type ServerStatus, } from '../../../src/lsp/cli.js'; // Mock child_process vi.mock('child_process', () => ({ execSync: vi.fn(), spawnSync: vi.fn(), })); // Mock server module vi.mock('../../../src/lsp/server.js', () => ({ getUniqueServers: vi.fn(() => [ { id: 'typescript-language-server', languages: ['typescript', 'javascript'], config: { displayName: 'TypeScript Language Server', description: 'TypeScript/JavaScript 语言服务器', command: 'typescript-language-server', install: { npm: 'typescript-language-server typescript', }, }, }, { id: 'pylsp', languages: ['python'], config: { displayName: 'Python LSP Server', description: 'Python 语言服务器', command: 'pylsp', install: { pip: 'python-lsp-server', manual: '也可以使用: pip3 install python-lsp-server', }, }, }, { id: 'gopls', languages: ['go'], config: { displayName: 'Go Language Server', description: 'Go 语言服务器', command: 'gopls', install: { go: 'golang.org/x/tools/gopls@latest', }, }, }, ]), })); import { execSync, spawnSync } from 'child_process'; describe('LSP CLI - LSP 命令行工具', () => { let consoleLogSpy: ReturnType; let consoleErrorSpy: ReturnType; beforeEach(() => { vi.clearAllMocks(); consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); // 默认命令存在 vi.mocked(execSync).mockImplementation((cmd: string) => { if (cmd.includes('which npm')) return Buffer.from('/usr/bin/npm'); if (cmd.includes('which pip3')) return Buffer.from('/usr/bin/pip3'); if (cmd.includes('which go')) return Buffer.from('/usr/bin/go'); throw new Error('Command not found'); }); }); afterEach(() => { consoleLogSpy.mockRestore(); consoleErrorSpy.mockRestore(); }); describe('listServers - 列出服务器', () => { it('返回所有服务器状态', () => { const servers = listServers(); expect(servers).toHaveLength(3); expect(servers[0].id).toBe('typescript-language-server'); expect(servers[1].id).toBe('pylsp'); expect(servers[2].id).toBe('gopls'); }); it('检测已安装的服务器', () => { // 只有 typescript-language-server 安装了 vi.mocked(execSync).mockImplementation((cmd: string) => { if (cmd === 'which typescript-language-server') return Buffer.from('/usr/bin/tsc'); throw new Error('not found'); }); const servers = listServers(); expect(servers[0].installed).toBe(true); expect(servers[1].installed).toBe(false); expect(servers[2].installed).toBe(false); }); it('包含服务器详细信息', () => { const servers = listServers(); const tsServer = servers[0]; expect(tsServer.displayName).toBe('TypeScript Language Server'); expect(tsServer.description).toContain('TypeScript'); expect(tsServer.command).toBe('typescript-language-server'); expect(tsServer.languages).toContain('typescript'); expect(tsServer.install).toBeDefined(); }); }); describe('printServerList - 打印服务器列表', () => { it('输出格式化的服务器列表', () => { printServerList(); expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('语言服务器状态')); expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('状态')); expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('服务器')); }); it('显示已安装数量统计', () => { printServerList(); const calls = consoleLogSpy.mock.calls.flat().join('\n'); expect(calls).toContain('已安装'); }); }); describe('installServer - 安装服务器', () => { it('服务器不存在时返回 false', async () => { const result = await installServer('non-existent'); expect(result).toBe(false); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('未找到服务器')); }); it('服务器已安装时返回 true', async () => { vi.mocked(execSync).mockImplementation((cmd: string) => { if (cmd === 'which typescript-language-server') return Buffer.from('/usr/bin/tsc'); if (cmd.includes('which npm')) return Buffer.from('/usr/bin/npm'); throw new Error('not found'); }); const result = await installServer('typescript-language-server'); expect(result).toBe(true); expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('已安装')); }); it('使用 npm 安装 TypeScript 服务器', async () => { vi.mocked(execSync).mockImplementation((cmd: string) => { if (cmd === 'which typescript-language-server') throw new Error('not found'); if (cmd.includes('which npm')) return Buffer.from('/usr/bin/npm'); throw new Error('not found'); }); vi.mocked(spawnSync).mockReturnValue({ status: 0 } as any); const result = await installServer('typescript-language-server'); expect(result).toBe(true); expect(spawnSync).toHaveBeenCalledWith( expect.stringContaining('npm install -g'), expect.any(Object) ); }); it('安装失败时返回 false', async () => { vi.mocked(execSync).mockImplementation((cmd: string) => { if (cmd === 'which typescript-language-server') throw new Error('not found'); if (cmd.includes('which npm')) return Buffer.from('/usr/bin/npm'); throw new Error('not found'); }); vi.mocked(spawnSync).mockReturnValue({ status: 1 } as any); const result = await installServer('typescript-language-server'); expect(result).toBe(false); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('安装失败')); }); it('按显示名称查找服务器', async () => { vi.mocked(execSync).mockImplementation((cmd: string) => { if (cmd === 'which typescript-language-server') return Buffer.from('/usr/bin/tsc'); if (cmd.includes('which npm')) return Buffer.from('/usr/bin/npm'); throw new Error('not found'); }); const result = await installServer('TypeScript Language Server'); expect(result).toBe(true); }); it('spawnSync 抛出异常时返回 false', async () => { vi.mocked(execSync).mockImplementation((cmd: string) => { if (cmd === 'which typescript-language-server') throw new Error('not found'); if (cmd.includes('which npm')) return Buffer.from('/usr/bin/npm'); throw new Error('not found'); }); vi.mocked(spawnSync).mockImplementation(() => { throw new Error('spawn error'); }); const result = await installServer('typescript-language-server'); expect(result).toBe(false); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('安装出错')); }); it('无法自动安装时显示手动说明', async () => { vi.mocked(execSync).mockImplementation((cmd: string) => { // pylsp 未安装,且没有可用的 pip if (cmd === 'which pylsp') throw new Error('not found'); throw new Error('not found'); }); const result = await installServer('pylsp'); expect(result).toBe(false); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('无法自动安装')); }); }); describe('installAllServers - 安装所有服务器', () => { it('所有服务器都已安装时显示完成', async () => { vi.mocked(execSync).mockReturnValue(Buffer.from('/usr/bin/cmd')); await installAllServers(); expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('都已安装')); }); it('安装未安装的服务器', async () => { vi.mocked(execSync).mockImplementation((cmd: string) => { // 所有服务器都未安装,但有 npm if (cmd.includes('which npm')) return Buffer.from('/usr/bin/npm'); throw new Error('not found'); }); vi.mocked(spawnSync).mockReturnValue({ status: 0 } as any); await installAllServers(); expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('将安装')); expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('安装完成')); }); }); describe('showServerInfo - 显示服务器信息', () => { it('显示已安装服务器的信息', () => { vi.mocked(execSync).mockImplementation((cmd: string) => { if (cmd === 'which typescript-language-server') return Buffer.from('/usr/bin/tsc'); if (cmd.includes('which npm')) return Buffer.from('/usr/bin/npm'); throw new Error('not found'); }); showServerInfo('typescript-language-server'); const calls = consoleLogSpy.mock.calls.flat().join('\n'); expect(calls).toContain('TypeScript Language Server'); expect(calls).toContain('已安装'); }); it('显示未安装服务器的安装命令', () => { vi.mocked(execSync).mockImplementation((cmd: string) => { if (cmd === 'which typescript-language-server') throw new Error('not found'); if (cmd.includes('which npm')) return Buffer.from('/usr/bin/npm'); throw new Error('not found'); }); showServerInfo('typescript-language-server'); const calls = consoleLogSpy.mock.calls.flat().join('\n'); expect(calls).toContain('未安装'); expect(calls).toContain('安装命令'); expect(calls).toContain('npm install'); }); it('服务器不存在时显示错误', () => { showServerInfo('non-existent'); expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('未找到服务器')); }); it('按显示名称查找服务器', () => { vi.mocked(execSync).mockReturnValue(Buffer.from('/usr/bin/cmd')); showServerInfo('Python LSP Server'); const calls = consoleLogSpy.mock.calls.flat().join('\n'); expect(calls).toContain('Python LSP Server'); }); }); describe('getInstallCommand - 安装命令选择', () => { it('pip 作为 Python 服务器的安装方式', async () => { vi.mocked(execSync).mockImplementation((cmd: string) => { if (cmd === 'which pylsp') throw new Error('not found'); if (cmd.includes('which pip3')) return Buffer.from('/usr/bin/pip3'); throw new Error('not found'); }); vi.mocked(spawnSync).mockReturnValue({ status: 0 } as any); await installServer('pylsp'); expect(spawnSync).toHaveBeenCalledWith( expect.stringContaining('pip3 install'), expect.any(Object) ); }); it('go install 作为 Go 服务器的安装方式', async () => { vi.mocked(execSync).mockImplementation((cmd: string) => { if (cmd === 'which gopls') throw new Error('not found'); if (cmd.includes('which go')) return Buffer.from('/usr/bin/go'); throw new Error('not found'); }); vi.mocked(spawnSync).mockReturnValue({ status: 0 } as any); await installServer('gopls'); expect(spawnSync).toHaveBeenCalledWith( expect.stringContaining('go install'), expect.any(Object) ); }); }); }); describe('ServerStatus 类型', () => { it('包含所有必要字段', () => { const status: ServerStatus = { id: 'test-server', displayName: 'Test Server', description: 'A test server', command: 'test-cmd', installed: true, languages: ['typescript'], install: { npm: 'test-package' }, }; expect(status.id).toBeDefined(); expect(status.displayName).toBeDefined(); expect(status.command).toBeDefined(); expect(status.installed).toBeDefined(); expect(status.languages).toBeDefined(); expect(status.install).toBeDefined(); }); });