import { describe, expect, test } from 'bun:test'; import { COMMANDS } from '@celilo/core'; import type { CommandDef } from '@celilo/core'; import { generateRichZshCompletion } from './generate-zsh-completion'; describe('Zsh Completion Generator', () => { const output = generateRichZshCompletion(COMMANDS); test('produces valid compdef header', () => { expect(output).toMatch(/^#compdef celilo/); }); test('contains auto-generated notice', () => { expect(output).toContain('Auto-generated by: celilo completion zsh'); expect(output).toContain('Do not edit manually'); }); test('contains main _celilo function', () => { expect(output).toContain('_celilo()'); expect(output).toContain('_celilo_commands()'); }); test('contains all top-level commands in _celilo_commands', () => { for (const cmd of COMMANDS) { expect(output).toContain(`'${cmd.name}:`); } }); test('generates functions for all commands with subcommands', () => { function checkSubcommands(commands: CommandDef[], path: string[]): void { for (const cmd of commands) { if (cmd.subcommands && cmd.subcommands.length > 0) { const fnName = `_celilo${path.length > 0 ? `_${[...path, cmd.name].join('_')}` : `_${cmd.name}`}`; expect(output).toContain(`${fnName}()`); expect(output).toContain(`${fnName}_commands()`); // Recurse checkSubcommands(cmd.subcommands, [...path, cmd.name]); } } } checkSubcommands(COMMANDS, []); }); test('generates dynamic completion functions for database-backed args', () => { expect(output).toContain('_celilo_module_ids()'); expect(output).toContain('_celilo_service_ids()'); expect(output).toContain('_celilo_machine_hostnames()'); expect(output).toContain('_celilo_capability_names()'); expect(output).toContain('_celilo_config_keys()'); expect(output).toContain('_celilo_system_config_keys()'); expect(output).toContain('_celilo_system_secret_keys()'); }); test('dynamic completions query sqlite3', () => { expect(output).toContain('sqlite3 "$db_path"'); expect(output).toContain('SELECT id FROM modules'); expect(output).toContain('SELECT service_id, name FROM container_services'); expect(output).toContain('SELECT hostname FROM machines'); expect(output).toContain('SELECT capability_name, module_id FROM capabilities'); }); test('includes flag completions for commands with flags', () => { // Module import has --target and --auto-generate-secrets expect(output).toContain('--target'); expect(output).toContain('--auto-generate-secrets'); // Machine add has --ip, --ssh-user, etc. expect(output).toContain('--ip'); expect(output).toContain('--ssh-user'); expect(output).toContain('--ssh-key-file'); // Service remove has --force expect(output).toContain('--force'); }); test('ends with _celilo invocation', () => { expect(output.trimEnd()).toMatch(/_celilo "\$@"$/); }); test('all subcommand descriptions are present', () => { function escapeDesc(text: string): string { return text.replace(/\\/g, '\\\\').replace(/:/g, '\\:').replace(/'/g, "'\\''"); } function checkDescriptions(commands: CommandDef[]): void { for (const cmd of commands) { if (cmd.subcommands) { for (const sub of cmd.subcommands) { // Description should appear (escaped) in the _commands function expect(output).toContain(escapeDesc(sub.description)); } checkDescriptions(cmd.subcommands); } } } checkDescriptions(COMMANDS); }); test('generated completion is valid zsh syntax', () => { // Recurrence gate: any unescaped quote/redirect in a description breaks the // whole file (an apostrophe in "account's" once did). zsh -n parses without // executing. Skip cleanly where zsh is unavailable (minimal CI images). let zsh: ReturnType; try { zsh = Bun.spawnSync(['zsh', '-nc', output]); } catch { return; // ponytail: no zsh here — nothing to check } if (zsh.exitCode === null) return; expect(zsh.stderr?.toString() ?? '').toBe(''); expect(zsh.exitCode).toBe(0); }); });