- `create server/profile/project` with all CLI flags (kubectl parity) - `edit server/profile/project` opens $EDITOR for in-flight editing - `get -o yaml/json` now outputs apply-compatible format (strips internal fields, wraps in resource key) - `describe` shows visually clean sectioned output with aligned columns - Extract shared utilities (resolveResource, resolveNameOrId, stripInternalFields) - Instances are immutable (no create/edit, like pods) - Full test coverage for create, edit, and updated describe/get Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
130 lines
4.2 KiB
TypeScript
130 lines
4.2 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { createDescribeCommand } from '../../src/commands/describe.js';
|
|
import type { DescribeCommandDeps } from '../../src/commands/describe.js';
|
|
import type { ApiClient } from '../../src/api-client.js';
|
|
|
|
function mockClient(): ApiClient {
|
|
return {
|
|
get: vi.fn(async () => []),
|
|
post: vi.fn(async () => ({})),
|
|
put: vi.fn(async () => ({})),
|
|
delete: vi.fn(async () => {}),
|
|
} as unknown as ApiClient;
|
|
}
|
|
|
|
function makeDeps(item: unknown = {}): DescribeCommandDeps & { output: string[] } {
|
|
const output: string[] = [];
|
|
return {
|
|
output,
|
|
client: mockClient(),
|
|
fetchResource: vi.fn(async () => item),
|
|
log: (...args: string[]) => output.push(args.join(' ')),
|
|
};
|
|
}
|
|
|
|
describe('describe command', () => {
|
|
it('shows detailed server info with sections', async () => {
|
|
const deps = makeDeps({
|
|
id: 'srv-1',
|
|
name: 'slack',
|
|
transport: 'STDIO',
|
|
packageName: '@slack/mcp',
|
|
dockerImage: null,
|
|
envTemplate: [],
|
|
createdAt: '2025-01-01',
|
|
});
|
|
const cmd = createDescribeCommand(deps);
|
|
await cmd.parseAsync(['node', 'test', 'server', 'srv-1']);
|
|
|
|
expect(deps.fetchResource).toHaveBeenCalledWith('servers', 'srv-1');
|
|
const text = deps.output.join('\n');
|
|
expect(text).toContain('=== Server: slack ===');
|
|
expect(text).toContain('Name:');
|
|
expect(text).toContain('slack');
|
|
expect(text).toContain('Transport:');
|
|
expect(text).toContain('STDIO');
|
|
expect(text).toContain('Package:');
|
|
expect(text).toContain('@slack/mcp');
|
|
expect(text).toContain('Metadata:');
|
|
expect(text).toContain('ID:');
|
|
});
|
|
|
|
it('resolves resource aliases', async () => {
|
|
const deps = makeDeps({ id: 'p1' });
|
|
const cmd = createDescribeCommand(deps);
|
|
await cmd.parseAsync(['node', 'test', 'prof', 'p1']);
|
|
expect(deps.fetchResource).toHaveBeenCalledWith('profiles', 'p1');
|
|
});
|
|
|
|
it('outputs JSON format', async () => {
|
|
const deps = makeDeps({ id: 'srv-1', name: 'slack' });
|
|
const cmd = createDescribeCommand(deps);
|
|
await cmd.parseAsync(['node', 'test', 'server', 'srv-1', '-o', 'json']);
|
|
|
|
const parsed = JSON.parse(deps.output[0] ?? '');
|
|
expect(parsed.name).toBe('slack');
|
|
});
|
|
|
|
it('outputs YAML format', async () => {
|
|
const deps = makeDeps({ id: 'srv-1', name: 'slack' });
|
|
const cmd = createDescribeCommand(deps);
|
|
await cmd.parseAsync(['node', 'test', 'server', 'srv-1', '-o', 'yaml']);
|
|
expect(deps.output[0]).toContain('name: slack');
|
|
});
|
|
|
|
it('shows profile with permissions and env overrides', async () => {
|
|
const deps = makeDeps({
|
|
id: 'p1',
|
|
name: 'production',
|
|
serverId: 'srv-1',
|
|
permissions: ['read', 'write'],
|
|
envOverrides: { FOO: 'bar', SECRET: 's3cr3t' },
|
|
createdAt: '2025-01-01',
|
|
});
|
|
const cmd = createDescribeCommand(deps);
|
|
await cmd.parseAsync(['node', 'test', 'profile', 'p1']);
|
|
|
|
const text = deps.output.join('\n');
|
|
expect(text).toContain('=== Profile: production ===');
|
|
expect(text).toContain('read, write');
|
|
expect(text).toContain('Environment Overrides:');
|
|
expect(text).toContain('FOO');
|
|
expect(text).toContain('bar');
|
|
});
|
|
|
|
it('shows project detail', async () => {
|
|
const deps = makeDeps({
|
|
id: 'proj-1',
|
|
name: 'my-project',
|
|
description: 'A test project',
|
|
ownerId: 'user-1',
|
|
createdAt: '2025-01-01',
|
|
});
|
|
const cmd = createDescribeCommand(deps);
|
|
await cmd.parseAsync(['node', 'test', 'project', 'proj-1']);
|
|
|
|
const text = deps.output.join('\n');
|
|
expect(text).toContain('=== Project: my-project ===');
|
|
expect(text).toContain('A test project');
|
|
expect(text).toContain('user-1');
|
|
});
|
|
|
|
it('shows instance detail with container info', async () => {
|
|
const deps = makeDeps({
|
|
id: 'inst-1',
|
|
serverId: 'srv-1',
|
|
status: 'RUNNING',
|
|
containerId: 'abc123',
|
|
port: 3000,
|
|
createdAt: '2025-01-01',
|
|
});
|
|
const cmd = createDescribeCommand(deps);
|
|
await cmd.parseAsync(['node', 'test', 'instance', 'inst-1']);
|
|
|
|
const text = deps.output.join('\n');
|
|
expect(text).toContain('=== Instance: inst-1 ===');
|
|
expect(text).toContain('RUNNING');
|
|
expect(text).toContain('abc123');
|
|
});
|
|
});
|