121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
|
|
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();
|
||
|
|
});
|
||
|
|
});
|