import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import fs from 'fs'; import os from 'os'; import path from 'path'; import { getCompletionSuggestions } from '../../../src/commands/completion-backend'; const originalHome = process.env.HOME; const originalUserProfile = process.env.USERPROFILE; const originalCcsHome = process.env.CCS_HOME; let tempHome = ''; function suggestionValues( tokensBeforeCurrent: string[], current = '', shell: 'bash' | 'fish' | 'powershell' | 'zsh' = 'bash' ): string[] { return getCompletionSuggestions({ tokensBeforeCurrent, current, shell }).map( (entry) => entry.value ); } beforeEach(() => { tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-completion-')); fs.mkdirSync(path.join(tempHome, '.ccs'), { recursive: true }); fs.writeFileSync( path.join(tempHome, '.ccs', 'config.json'), JSON.stringify( { profiles: { localglm: '~/.ccs/localglm.settings.json', }, cliproxy: { 'my-codex': { provider: 'codex', settings: '~/.ccs/my-codex.settings.json', }, }, }, null, 2 ) ); fs.writeFileSync( path.join(tempHome, '.ccs', 'profiles.json'), JSON.stringify( { profiles: { work: { type: 'account', created: '2026-04-02T00:00:00.000Z' }, }, default: 'work', }, null, 2 ) ); process.env.HOME = tempHome; process.env.USERPROFILE = tempHome; process.env.CCS_HOME = tempHome; }); afterEach(() => { if (originalHome === undefined) delete process.env.HOME; else process.env.HOME = originalHome; if (originalUserProfile === undefined) delete process.env.USERPROFILE; else process.env.USERPROFILE = originalUserProfile; if (originalCcsHome === undefined) delete process.env.CCS_HOME; else process.env.CCS_HOME = originalCcsHome; fs.rmSync(tempHome, { recursive: true, force: true }); }); describe('completion backend', () => { test('includes current root commands and dynamic profiles at the top level', () => { const values = suggestionValues([]); expect(values).toContain('config'); expect(values).toContain('docker'); expect(values).toContain('tokens'); expect(values).toContain('migrate'); expect(values).toContain('cursor'); expect(values).toContain('copilot'); expect(values).toContain('gemini'); expect(values).toContain('gitlab'); expect(values).toContain('codebuddy'); expect(values).toContain('kilo'); expect(values).toContain('localglm'); expect(values).toContain('work'); expect(values).toContain('my-codex'); expect(values).not.toContain('__complete'); expect(values).not.toContain('--install'); expect(values).not.toContain('--uninstall'); }); test('suggests help topics from the help command', () => { const values = suggestionValues(['help']); expect(values).toContain('profiles'); expect(values).toContain('providers'); expect(values).toContain('completion'); expect(values).toContain('targets'); expect(values).not.toContain('api'); expect(values).not.toContain('__complete'); }); test('suggests lifecycle subcommands for api', () => { const values = suggestionValues(['api']); expect(values).toEqual( expect.arrayContaining(['create', 'discover', 'copy', 'export', 'import', 'remove']) ); }); test('suggests account profiles for auth show', () => { const values = suggestionValues(['auth', 'show']); expect(values).toContain('work'); expect(values).toContain('--json'); }); test('suggests default lane and account profiles for auth backup', () => { const values = suggestionValues(['auth', 'backup']); expect(values).toContain('default'); expect(values).toContain('work'); expect(values).toContain('--json'); }); test('suggests cliproxy variants for variant-scoped commands', () => { const values = suggestionValues(['cliproxy', 'edit']); expect(values).toContain('my-codex'); }); test('suggests env format values after the format flag', () => { const values = suggestionValues(['env', '--format']); expect(values).toEqual( expect.arrayContaining(['openai', 'anthropic', 'raw', 'claude-extension']) ); }); test('includes live doctor and cliproxy flags from the shared catalog', () => { expect(suggestionValues(['doctor'])).toEqual(expect.arrayContaining(['--fix', '-f'])); expect(suggestionValues(['cliproxy'])).toEqual(expect.arrayContaining(['remove', '--backend'])); }); test('treats cursor as a provider shortcut in completion', () => { const values = suggestionValues(['cursor']); expect(values).toEqual( expect.arrayContaining(['--auth', '--accounts', '--config', '--logout']) ); expect(values).not.toContain('probe'); expect(values).not.toContain('start'); }); test('filters suggestions by the current token prefix', () => { const values = suggestionValues([], 'do'); expect(values).toEqual(expect.arrayContaining(['docker', 'doctor'])); expect(values).not.toContain('tokens'); }); });