2026-02-21 04:55:45 +00:00
|
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
|
|
|
import { createDescribeCommand } from '../../src/commands/describe.js';
|
|
|
|
|
import type { DescribeCommandDeps } from '../../src/commands/describe.js';
|
2026-02-22 14:33:25 +00:00
|
|
|
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;
|
|
|
|
|
}
|
2026-02-21 04:55:45 +00:00
|
|
|
|
|
|
|
|
function makeDeps(item: unknown = {}): DescribeCommandDeps & { output: string[] } {
|
|
|
|
|
const output: string[] = [];
|
|
|
|
|
return {
|
|
|
|
|
output,
|
2026-02-22 14:33:25 +00:00
|
|
|
client: mockClient(),
|
2026-02-21 04:55:45 +00:00
|
|
|
fetchResource: vi.fn(async () => item),
|
|
|
|
|
log: (...args: string[]) => output.push(args.join(' ')),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('describe command', () => {
|
2026-02-22 14:33:25 +00:00
|
|
|
it('shows detailed server info with sections', async () => {
|
2026-02-21 04:55:45 +00:00
|
|
|
const deps = makeDeps({
|
|
|
|
|
id: 'srv-1',
|
|
|
|
|
name: 'slack',
|
|
|
|
|
transport: 'STDIO',
|
|
|
|
|
packageName: '@slack/mcp',
|
|
|
|
|
dockerImage: null,
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
env: [],
|
2026-02-22 14:33:25 +00:00
|
|
|
createdAt: '2025-01-01',
|
2026-02-21 04:55:45 +00:00
|
|
|
});
|
|
|
|
|
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');
|
2026-02-22 14:33:25 +00:00
|
|
|
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:');
|
2026-02-21 04:55:45 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('resolves resource aliases', async () => {
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
const deps = makeDeps({ id: 's1' });
|
2026-02-21 04:55:45 +00:00
|
|
|
const cmd = createDescribeCommand(deps);
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
await cmd.parseAsync(['node', 'test', 'sec', 's1']);
|
|
|
|
|
expect(deps.fetchResource).toHaveBeenCalledWith('secrets', 's1');
|
2026-02-21 04:55:45 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-22 14:33:25 +00:00
|
|
|
it('shows project detail', async () => {
|
2026-02-21 04:55:45 +00:00
|
|
|
const deps = makeDeps({
|
2026-02-22 14:33:25 +00:00
|
|
|
id: 'proj-1',
|
|
|
|
|
name: 'my-project',
|
|
|
|
|
description: 'A test project',
|
|
|
|
|
ownerId: 'user-1',
|
|
|
|
|
createdAt: '2025-01-01',
|
2026-02-21 04:55:45 +00:00
|
|
|
});
|
|
|
|
|
const cmd = createDescribeCommand(deps);
|
2026-02-22 14:33:25 +00:00
|
|
|
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');
|
|
|
|
|
});
|
|
|
|
|
|
feat: replace profiles with kubernetes-style secrets
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:40:58 +00:00
|
|
|
it('shows secret detail with masked values', async () => {
|
|
|
|
|
const deps = makeDeps({
|
|
|
|
|
id: 'sec-1',
|
|
|
|
|
name: 'ha-creds',
|
|
|
|
|
data: { TOKEN: 'abc123', URL: 'https://ha.local' },
|
|
|
|
|
createdAt: '2025-01-01',
|
|
|
|
|
});
|
|
|
|
|
const cmd = createDescribeCommand(deps);
|
|
|
|
|
await cmd.parseAsync(['node', 'test', 'secret', 'sec-1']);
|
|
|
|
|
|
|
|
|
|
const text = deps.output.join('\n');
|
|
|
|
|
expect(text).toContain('=== Secret: ha-creds ===');
|
|
|
|
|
expect(text).toContain('TOKEN');
|
|
|
|
|
expect(text).toContain('***');
|
|
|
|
|
expect(text).not.toContain('abc123');
|
|
|
|
|
expect(text).toContain('use --show-values to reveal');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows secret detail with revealed values when --show-values', async () => {
|
|
|
|
|
const deps = makeDeps({
|
|
|
|
|
id: 'sec-1',
|
|
|
|
|
name: 'ha-creds',
|
|
|
|
|
data: { TOKEN: 'abc123' },
|
|
|
|
|
createdAt: '2025-01-01',
|
|
|
|
|
});
|
|
|
|
|
const cmd = createDescribeCommand(deps);
|
|
|
|
|
await cmd.parseAsync(['node', 'test', 'secret', 'sec-1', '--show-values']);
|
|
|
|
|
|
|
|
|
|
const text = deps.output.join('\n');
|
|
|
|
|
expect(text).toContain('abc123');
|
|
|
|
|
expect(text).not.toContain('***');
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-22 14:33:25 +00:00
|
|
|
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']);
|
2026-02-21 04:55:45 +00:00
|
|
|
|
|
|
|
|
const text = deps.output.join('\n');
|
2026-02-22 14:33:25 +00:00
|
|
|
expect(text).toContain('=== Instance: inst-1 ===');
|
|
|
|
|
expect(text).toContain('RUNNING');
|
|
|
|
|
expect(text).toContain('abc123');
|
2026-02-21 04:55:45 +00:00
|
|
|
});
|
|
|
|
|
});
|