import { describe, it, expect, vi, beforeEach } from 'vitest'; import { createProjectCommand } from '../../src/commands/project.js'; import type { ApiClient } from '../../src/api-client.js'; function mockClient(): ApiClient { return { get: vi.fn(async () => []), post: vi.fn(async () => ({ id: 'proj-1', name: 'my-project' })), put: vi.fn(async () => ({})), delete: vi.fn(async () => {}), } as unknown as ApiClient; } describe('project command', () => { let client: ReturnType; let output: string[]; const log = (...args: unknown[]) => output.push(args.map(String).join(' ')); beforeEach(() => { client = mockClient(); output = []; }); describe('list', () => { it('shows no projects message when empty', async () => { const cmd = createProjectCommand({ client, log }); await cmd.parseAsync(['list'], { from: 'user' }); expect(output.join('\n')).toContain('No projects found'); }); it('shows project table', async () => { vi.mocked(client.get).mockResolvedValue([ { id: 'proj-1', name: 'dev', description: 'Dev project', ownerId: 'user-1', createdAt: '2025-01-01' }, ]); const cmd = createProjectCommand({ client, log }); await cmd.parseAsync(['list'], { from: 'user' }); expect(output.join('\n')).toContain('proj-1'); expect(output.join('\n')).toContain('dev'); }); it('outputs json', async () => { vi.mocked(client.get).mockResolvedValue([{ id: 'proj-1', name: 'dev' }]); const cmd = createProjectCommand({ client, log }); await cmd.parseAsync(['list', '-o', 'json'], { from: 'user' }); expect(output[0]).toContain('"id"'); }); }); describe('create', () => { it('creates a project', async () => { const cmd = createProjectCommand({ client, log }); await cmd.parseAsync(['create', 'my-project', '-d', 'A test project'], { from: 'user' }); expect(client.post).toHaveBeenCalledWith('/api/v1/projects', { name: 'my-project', description: 'A test project', }); expect(output.join('\n')).toContain("Project 'my-project' created"); }); }); describe('delete', () => { it('deletes a project', async () => { const cmd = createProjectCommand({ client, log }); await cmd.parseAsync(['delete', 'proj-1'], { from: 'user' }); expect(client.delete).toHaveBeenCalledWith('/api/v1/projects/proj-1'); expect(output.join('\n')).toContain('deleted'); }); }); describe('show', () => { it('shows project details', async () => { vi.mocked(client.get).mockImplementation(async (url: string) => { if (url.endsWith('/profiles')) return []; return { id: 'proj-1', name: 'dev', description: 'Dev project', ownerId: 'user-1', createdAt: '2025-01-01' }; }); const cmd = createProjectCommand({ client, log }); await cmd.parseAsync(['show', 'proj-1'], { from: 'user' }); expect(output.join('\n')).toContain('Name: dev'); expect(output.join('\n')).toContain('ID: proj-1'); }); }); describe('profiles', () => { it('lists profiles for a project', async () => { vi.mocked(client.get).mockResolvedValue([ { id: 'prof-1', name: 'default', serverId: 'srv-1' }, ]); const cmd = createProjectCommand({ client, log }); await cmd.parseAsync(['profiles', 'proj-1'], { from: 'user' }); expect(client.get).toHaveBeenCalledWith('/api/v1/projects/proj-1/profiles'); expect(output.join('\n')).toContain('default'); }); it('shows empty message when no profiles', async () => { const cmd = createProjectCommand({ client, log }); await cmd.parseAsync(['profiles', 'proj-1'], { from: 'user' }); expect(output.join('\n')).toContain('No profiles assigned'); }); }); describe('set-profiles', () => { it('sets profiles for a project', async () => { const cmd = createProjectCommand({ client, log }); await cmd.parseAsync(['set-profiles', 'proj-1', 'prof-1', 'prof-2'], { from: 'user' }); expect(client.put).toHaveBeenCalledWith('/api/v1/projects/proj-1/profiles', { profileIds: ['prof-1', 'prof-2'], }); expect(output.join('\n')).toContain('2 profile(s)'); }); }); });