/** * Verifies the CLI rename from `celilo backup create ` to * `celilo module backup `: * - The new shape routes to the same handler. * - The legacy shape still works but prints a deprecation banner. * - The banner is silenced by CELILO_SUPPRESS_DEPRECATION=1. * * Doesn't invoke a real backup — both routes resolve to handleBackupCreate * which fails when there's no module record / no storage. That failure path * proves routing landed at the right handler. */ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { closeDb } from '../db/client'; import { runMigrations } from '../db/migrate'; import { resetTestDbPath } from '../test-utils/db-path'; import { runCli } from './index'; describe('celilo module backup (renamed surface)', () => { let dir: string; let warnSpy: ((...args: unknown[]) => void) | null = null; let warnings: string[] = []; beforeEach(async () => { dir = mkdtempSync(join(tmpdir(), 'celilo-rename-test-')); process.env.CELILO_DB_PATH = join(dir, 'celilo.db'); process.env.CELILO_MASTER_KEY_PATH = join(dir, 'master.key'); delete process.env.CELILO_SUPPRESS_DEPRECATION; await runMigrations(process.env.CELILO_DB_PATH); warnings = []; warnSpy = (...args: unknown[]) => { warnings.push(args.map(String).join(' ')); }; const realWarn = console.warn; console.warn = warnSpy as typeof console.warn; // Restore via afterEach by holding the original in a closure. (warnSpy as unknown as { __restore: () => void }).__restore = () => { console.warn = realWarn; }; }); afterEach(() => { if (warnSpy) { (warnSpy as unknown as { __restore: () => void }).__restore(); warnSpy = null; } closeDb(); resetTestDbPath(); delete process.env.CELILO_MASTER_KEY_PATH; delete process.env.CELILO_SUPPRESS_DEPRECATION; try { rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ } }); test('celilo module backup routes to the backup-create handler', async () => { const result = await runCli(['node', 'celilo', 'module', 'backup', 'no-such-module']); // Module doesn't exist → handler returns an error. Routing reached the // handler if we get THAT error (rather than "Unknown module subcommand"). expect(result.success).toBe(false); const err = result.success ? '' : (result.error ?? ''); expect(err).not.toContain('Unknown module subcommand'); // No deprecation banner on the canonical surface. expect(warnings.join('\n')).not.toContain('deprecated'); }); test('celilo backup create still works AND prints a deprecation banner', async () => { const result = await runCli(['node', 'celilo', 'backup', 'create', 'no-such-module']); // Reaches the same handler (same failure mode as the canonical route). expect(result.success).toBe(false); const err = result.success ? '' : (result.error ?? ''); expect(err).not.toContain('Unknown'); // Banner present, points at the canonical command. const banner = warnings.find((w) => w.includes('deprecated')); expect(banner).toBeDefined(); expect(banner ?? '').toContain('celilo module backup no-such-module'); }); test('CELILO_SUPPRESS_DEPRECATION=1 silences the deprecation banner', async () => { process.env.CELILO_SUPPRESS_DEPRECATION = '1'; await runCli(['node', 'celilo', 'backup', 'create', 'no-such-module']); expect(warnings.join('\n')).not.toContain('deprecated'); }); test('celilo backup list still routes (only create was renamed)', async () => { const result = await runCli(['node', 'celilo', 'backup', 'list']); // Empty DB → empty list, success. expect(result.success).toBe(true); }); });