- Remove ProjectMember model entirely (RBAC manages project access) - Add 'expose' RBAC role for /mcp-config endpoint access (edit implies expose) - Rename CLI flags: --llm-provider → --proxy-mode-llm-provider, --llm-model → --proxy-mode-llm-model - Add attach-server / detach-server CLI commands (mcpctl --project NAME attach-server SERVER) - Add POST/DELETE /api/v1/projects/:id/servers endpoints for server attach/detach - Remove members from backup/restore, apply, get, describe - Prisma migration to drop ProjectMember table Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
117 lines
3.9 KiB
TypeScript
117 lines
3.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { createCreateCommand } from '../../src/commands/create.js';
|
|
import { createGetCommand } from '../../src/commands/get.js';
|
|
import { createDescribeCommand } from '../../src/commands/describe.js';
|
|
import { type ApiClient, ApiError } from '../../src/api-client.js';
|
|
|
|
function mockClient(): ApiClient {
|
|
return {
|
|
get: vi.fn(async () => []),
|
|
post: vi.fn(async () => ({ id: 'new-id', name: 'test' })),
|
|
put: vi.fn(async () => ({})),
|
|
delete: vi.fn(async () => {}),
|
|
} as unknown as ApiClient;
|
|
}
|
|
|
|
describe('project with new fields', () => {
|
|
let client: ReturnType<typeof mockClient>;
|
|
let output: string[];
|
|
const log = (...args: unknown[]) => output.push(args.map(String).join(' '));
|
|
|
|
beforeEach(() => {
|
|
client = mockClient();
|
|
output = [];
|
|
});
|
|
|
|
describe('create project with enhanced options', () => {
|
|
it('creates project with proxy mode and servers', async () => {
|
|
const cmd = createCreateCommand({ client, log });
|
|
await cmd.parseAsync([
|
|
'project', 'smart-home',
|
|
'-d', 'Smart home project',
|
|
'--proxy-mode', 'filtered',
|
|
'--proxy-mode-llm-provider', 'gemini-cli',
|
|
'--proxy-mode-llm-model', 'gemini-2.0-flash',
|
|
'--server', 'my-grafana',
|
|
'--server', 'my-ha',
|
|
], { from: 'user' });
|
|
|
|
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', expect.objectContaining({
|
|
name: 'smart-home',
|
|
description: 'Smart home project',
|
|
proxyMode: 'filtered',
|
|
llmProvider: 'gemini-cli',
|
|
llmModel: 'gemini-2.0-flash',
|
|
servers: ['my-grafana', 'my-ha'],
|
|
}));
|
|
});
|
|
|
|
it('defaults proxy mode to direct', async () => {
|
|
const cmd = createCreateCommand({ client, log });
|
|
await cmd.parseAsync(['project', 'basic'], { from: 'user' });
|
|
|
|
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', expect.objectContaining({
|
|
proxyMode: 'direct',
|
|
}));
|
|
});
|
|
});
|
|
|
|
describe('get projects shows new columns', () => {
|
|
it('shows MODE and SERVERS columns', async () => {
|
|
const deps = {
|
|
output: [] as string[],
|
|
fetchResource: vi.fn(async () => [{
|
|
id: 'proj-1',
|
|
name: 'smart-home',
|
|
description: 'Test',
|
|
proxyMode: 'filtered',
|
|
ownerId: 'user-1',
|
|
servers: [{ server: { name: 'grafana' } }, { server: { name: 'ha' } }],
|
|
}]),
|
|
log: (...args: string[]) => deps.output.push(args.join(' ')),
|
|
};
|
|
const cmd = createGetCommand(deps);
|
|
await cmd.parseAsync(['node', 'test', 'projects']);
|
|
|
|
const text = deps.output.join('\n');
|
|
expect(text).toContain('MODE');
|
|
expect(text).toContain('SERVERS');
|
|
expect(text).toContain('smart-home');
|
|
});
|
|
});
|
|
|
|
describe('describe project shows full detail', () => {
|
|
it('shows servers and proxy config', async () => {
|
|
const deps = {
|
|
output: [] as string[],
|
|
client: mockClient(),
|
|
fetchResource: vi.fn(async () => ({
|
|
id: 'proj-1',
|
|
name: 'smart-home',
|
|
description: 'Smart home',
|
|
proxyMode: 'filtered',
|
|
llmProvider: 'gemini-cli',
|
|
llmModel: 'gemini-2.0-flash',
|
|
ownerId: 'user-1',
|
|
servers: [
|
|
{ server: { name: 'my-grafana' } },
|
|
{ server: { name: 'my-ha' } },
|
|
],
|
|
createdAt: '2025-01-01',
|
|
updatedAt: '2025-01-01',
|
|
})),
|
|
log: (...args: string[]) => deps.output.push(args.join(' ')),
|
|
};
|
|
const cmd = createDescribeCommand(deps);
|
|
await cmd.parseAsync(['node', 'test', 'project', 'proj-1']);
|
|
|
|
const text = deps.output.join('\n');
|
|
expect(text).toContain('=== Project: smart-home ===');
|
|
expect(text).toContain('filtered');
|
|
expect(text).toContain('gemini-cli');
|
|
expect(text).toContain('my-grafana');
|
|
expect(text).toContain('my-ha');
|
|
});
|
|
});
|
|
});
|