import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import fs from 'node:fs'; import { createBackupCommand, createRestoreCommand } from '../../src/commands/backup.js'; const mockClient = { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn(), }; const log = vi.fn(); describe('backup command', () => { beforeEach(() => { vi.resetAllMocks(); }); afterEach(() => { // Clean up any created files try { fs.unlinkSync('test-backup.json'); } catch { /* ignore */ } }); it('creates backup command', () => { const cmd = createBackupCommand({ client: mockClient as never, log }); expect(cmd.name()).toBe('backup'); }); it('calls API and writes file', async () => { const bundle = { version: '1', servers: [], profiles: [], projects: [] }; mockClient.post.mockResolvedValue(bundle); const cmd = createBackupCommand({ client: mockClient as never, log }); await cmd.parseAsync(['-o', 'test-backup.json'], { from: 'user' }); expect(mockClient.post).toHaveBeenCalledWith('/api/v1/backup', {}); expect(fs.existsSync('test-backup.json')).toBe(true); expect(log).toHaveBeenCalledWith(expect.stringContaining('test-backup.json')); }); it('passes password when provided', async () => { mockClient.post.mockResolvedValue({ version: '1', servers: [], profiles: [], projects: [] }); const cmd = createBackupCommand({ client: mockClient as never, log }); await cmd.parseAsync(['-o', 'test-backup.json', '-p', 'secret'], { from: 'user' }); expect(mockClient.post).toHaveBeenCalledWith('/api/v1/backup', { password: 'secret' }); }); it('passes resource filter', async () => { mockClient.post.mockResolvedValue({ version: '1', servers: [], profiles: [], projects: [] }); const cmd = createBackupCommand({ client: mockClient as never, log }); await cmd.parseAsync(['-o', 'test-backup.json', '-r', 'servers,profiles'], { from: 'user' }); expect(mockClient.post).toHaveBeenCalledWith('/api/v1/backup', { resources: ['servers', 'profiles'], }); }); }); describe('restore command', () => { const testFile = 'test-restore-input.json'; beforeEach(() => { vi.resetAllMocks(); fs.writeFileSync(testFile, JSON.stringify({ version: '1', servers: [], profiles: [], projects: [], })); }); afterEach(() => { try { fs.unlinkSync(testFile); } catch { /* ignore */ } }); it('creates restore command', () => { const cmd = createRestoreCommand({ client: mockClient as never, log }); expect(cmd.name()).toBe('restore'); }); it('reads file and calls API', async () => { mockClient.post.mockResolvedValue({ serversCreated: 1, serversSkipped: 0, profilesCreated: 0, profilesSkipped: 0, projectsCreated: 0, projectsSkipped: 0, errors: [], }); const cmd = createRestoreCommand({ client: mockClient as never, log }); await cmd.parseAsync(['-i', testFile], { from: 'user' }); expect(mockClient.post).toHaveBeenCalledWith('/api/v1/restore', expect.objectContaining({ bundle: expect.objectContaining({ version: '1' }), conflictStrategy: 'skip', })); expect(log).toHaveBeenCalledWith('Restore complete:'); }); it('reports errors from restore', async () => { mockClient.post.mockResolvedValue({ serversCreated: 0, serversSkipped: 0, profilesCreated: 0, profilesSkipped: 0, projectsCreated: 0, projectsSkipped: 0, errors: ['Server "x" already exists'], }); const cmd = createRestoreCommand({ client: mockClient as never, log }); await cmd.parseAsync(['-i', testFile], { from: 'user' }); expect(log).toHaveBeenCalledWith(expect.stringContaining('Errors')); }); it('logs error for missing file', async () => { const cmd = createRestoreCommand({ client: mockClient as never, log }); await cmd.parseAsync(['-i', 'nonexistent.json'], { from: 'user' }); expect(log).toHaveBeenCalledWith(expect.stringContaining('not found')); expect(mockClient.post).not.toHaveBeenCalled(); }); });