@chanl/cli 2.0.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/agents.test.js +574 -0
- package/dist/__tests__/agents.test.js.map +1 -0
- package/dist/__tests__/auth.test.d.ts +2 -0
- package/dist/__tests__/auth.test.js +87 -0
- package/dist/__tests__/auth.test.js.map +1 -0
- package/dist/__tests__/calls.test.d.ts +2 -0
- package/dist/__tests__/calls.test.js +115 -0
- package/dist/__tests__/calls.test.js.map +1 -0
- package/dist/__tests__/chat.test.d.ts +2 -0
- package/dist/__tests__/chat.test.js +357 -0
- package/dist/__tests__/chat.test.js.map +1 -0
- package/dist/__tests__/cli-base.test.d.ts +2 -0
- package/dist/__tests__/cli-base.test.js +87 -0
- package/dist/__tests__/cli-base.test.js.map +1 -0
- package/dist/__tests__/cli-missing.test.d.ts +2 -0
- package/dist/__tests__/cli-missing.test.js +546 -0
- package/dist/__tests__/cli-missing.test.js.map +1 -0
- package/dist/__tests__/helpers.d.ts +234 -0
- package/dist/__tests__/helpers.js +314 -0
- package/dist/__tests__/helpers.js.map +1 -0
- package/dist/__tests__/mcp.test.d.ts +2 -0
- package/dist/__tests__/mcp.test.js +470 -0
- package/dist/__tests__/mcp.test.js.map +1 -0
- package/dist/__tests__/prompts.test.d.ts +2 -0
- package/dist/__tests__/prompts.test.js +227 -0
- package/dist/__tests__/prompts.test.js.map +1 -0
- package/dist/__tests__/scenarios.test.d.ts +2 -0
- package/dist/__tests__/scenarios.test.js +270 -0
- package/dist/__tests__/scenarios.test.js.map +1 -0
- package/dist/__tests__/tools.test.d.ts +2 -0
- package/dist/__tests__/tools.test.js +331 -0
- package/dist/__tests__/tools.test.js.map +1 -0
- package/dist/__tests__/toolsets.test.d.ts +2 -0
- package/dist/__tests__/toolsets.test.js +196 -0
- package/dist/__tests__/toolsets.test.js.map +1 -0
- package/dist/__tests__/workspaces.test.d.ts +2 -0
- package/dist/__tests__/workspaces.test.js +155 -0
- package/dist/__tests__/workspaces.test.js.map +1 -0
- package/dist/commands/agents.js +103 -13
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/chat.js +2 -2
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/config.js +24 -2
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/memory.js +137 -4
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/personas.js +3 -3
- package/dist/commands/personas.js.map +1 -1
- package/dist/commands/prompts.js +148 -1
- package/dist/commands/prompts.js.map +1 -1
- package/dist/commands/scenarios.js +2 -2
- package/dist/commands/scenarios.js.map +1 -1
- package/dist/commands/scorecards.js +3 -3
- package/dist/commands/scorecards.js.map +1 -1
- package/dist/commands/tools.js +5 -5
- package/dist/commands/tools.js.map +1 -1
- package/dist/utils/config-store.d.ts +8 -0
- package/dist/utils/config-store.js +18 -1
- package/dist/utils/config-store.js.map +1 -1
- package/dist/utils/sdk-factory.js +15 -3
- package/dist/utils/sdk-factory.js.map +1 -1
- package/package.json +4 -4
- package/dist/__tests__/cli.test.js +0 -2313
- package/dist/__tests__/cli.test.js.map +0 -1
- /package/dist/__tests__/{cli.test.d.ts → agents.test.d.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/helpers.ts"],"sourcesContent":["/**\n * Shared CLI test helpers\n *\n * Two parallel mocking strategies are supported:\n *\n * 1. **SDK-level mocking** (legacy, most files) — `makeMockSdk()` +\n * `vi.mock('@chanl/sdk', ...)` replaces the SDK entirely with spies.\n * Fast and simple but the tests never run real SDK code, so they miss\n * contract drift between CLI and SDK.\n *\n * 2. **Fetch-level mocking** (new, P0 files) — `installFetchMock()` +\n * `mockFetchResponse()` stubs only `global.fetch`. The real `ChanlSDK`\n * runs — real URL building, real auth header injection, real response\n * unwrapping, real error mapping. Tests assert on BOTH CLI output AND\n * the HTTP request the SDK built. This catches regressions where a\n * CLI handler passes the wrong argument shape to the SDK or the SDK\n * changes its endpoint path.\n *\n * NOTE: vi.mock('@chanl/sdk') and vi.mock('../utils/config-store') must be\n * declared in EACH test file (vitest hoists vi.mock calls to the top of the\n * file they're defined in — they do not carry across imports).\n *\n * For fetch-level tests, do NOT mock '@chanl/sdk' — let the real SDK run.\n * Only mock '../utils/config-store' and install the fetch mock in beforeEach.\n */\n\nimport { vi, expect, type Mock } from 'vitest';\nimport { createProgram } from '../cli';\nimport type { Tool, ToolExecution, Toolset } from '@chanl/sdk';\n\n// Helper to capture console output\nexport function captureOutput() {\n const logs: string[] = [];\n const errors: string[] = [];\n\n const originalLog = console.log;\n const originalError = console.error;\n\n console.log = (...args: unknown[]) => {\n logs.push(args.map(String).join(' '));\n };\n\n console.error = (...args: unknown[]) => {\n errors.push(args.map(String).join(' '));\n };\n\n return {\n logs,\n errors,\n restore: () => {\n console.log = originalLog;\n console.error = originalError;\n },\n };\n}\n\n// Helper to run CLI command\nexport async function runCli(args: string[]): Promise<{ exitCode: number; logs: string[]; errors: string[] }> {\n const output = captureOutput();\n const program = createProgram();\n\n // Reset exit code\n process.exitCode = undefined;\n\n try {\n await program.parseAsync(['node', 'chanl', ...args]);\n } catch {\n // Commander throws for some errors, which is expected\n }\n\n output.restore();\n\n return {\n exitCode: process.exitCode ?? 0,\n logs: output.logs,\n errors: output.errors,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Mock SDK factory\n// ---------------------------------------------------------------------------\n\nexport type MockSdk = {\n tools: {\n list: Mock;\n get: Mock;\n create: Mock;\n update: Mock;\n delete: Mock;\n execute: Mock;\n test: Mock;\n getExecutions: Mock;\n enable: Mock;\n disable: Mock;\n getCategories: Mock;\n };\n toolsets: {\n list: Mock;\n get: Mock;\n create: Mock;\n update: Mock;\n delete: Mock;\n enable: Mock;\n disable: Mock;\n manageTools: Mock;\n };\n workspace: {\n getAll: Mock;\n getById: Mock;\n };\n mcp: {\n getConfig: Mock;\n getStatus: Mock;\n listTools: Mock;\n callTool: Mock;\n getTool: Mock;\n };\n chat: {\n createSession: Mock;\n sendMessage: Mock;\n getMessages: Mock;\n endSession: Mock;\n deleteSession: Mock;\n session: Mock;\n };\n calls: {\n list: Mock;\n get: Mock;\n getTranscript: Mock;\n getMetrics: Mock;\n getAnalysis: Mock;\n analyze: Mock;\n import: Mock;\n delete: Mock;\n bulkDelete: Mock;\n };\n scenarios: {\n list: Mock;\n get: Mock;\n run: Mock;\n runAll: Mock;\n getExecution: Mock;\n getExecutions: Mock;\n getSimulations: Mock;\n };\n agents: {\n list: Mock;\n get: Mock;\n create: Mock;\n update: Mock;\n delete: Mock;\n sync: Mock;\n syncAll: Mock;\n getStats: Mock;\n importFromPlatform: Mock;\n publish: Mock;\n pushMcp: Mock;\n };\n prompts: {\n list: Mock;\n get: Mock;\n create: Mock;\n update: Mock;\n delete: Mock;\n getVersions: Mock;\n revert: Mock;\n resolve: Mock;\n apply: Mock;\n };\n};\n\nexport function makeMockSdk(): MockSdk {\n return {\n tools: {\n list: vi.fn(),\n get: vi.fn(),\n create: vi.fn(),\n update: vi.fn(),\n delete: vi.fn(),\n execute: vi.fn(),\n test: vi.fn(),\n getExecutions: vi.fn(),\n enable: vi.fn(),\n disable: vi.fn(),\n getCategories: vi.fn(),\n },\n toolsets: {\n list: vi.fn(),\n get: vi.fn(),\n create: vi.fn(),\n update: vi.fn(),\n delete: vi.fn(),\n enable: vi.fn(),\n disable: vi.fn(),\n manageTools: vi.fn(),\n },\n workspace: {\n getAll: vi.fn(),\n getById: vi.fn(),\n },\n mcp: {\n getConfig: vi.fn(),\n getStatus: vi.fn(),\n listTools: vi.fn(),\n callTool: vi.fn(),\n getTool: vi.fn(),\n },\n chat: {\n createSession: vi.fn(),\n sendMessage: vi.fn(),\n getMessages: vi.fn(),\n endSession: vi.fn(),\n deleteSession: vi.fn(),\n session: vi.fn(),\n },\n calls: {\n list: vi.fn(),\n get: vi.fn(),\n getTranscript: vi.fn(),\n getMetrics: vi.fn(),\n getAnalysis: vi.fn(),\n analyze: vi.fn(),\n import: vi.fn(),\n delete: vi.fn(),\n bulkDelete: vi.fn(),\n },\n scenarios: {\n list: vi.fn(),\n get: vi.fn(),\n run: vi.fn(),\n runAll: vi.fn(),\n getExecution: vi.fn(),\n getExecutions: vi.fn(),\n getSimulations: vi.fn(),\n },\n agents: {\n list: vi.fn(),\n get: vi.fn(),\n create: vi.fn(),\n update: vi.fn(),\n delete: vi.fn(),\n sync: vi.fn(),\n syncAll: vi.fn(),\n getStats: vi.fn(),\n importFromPlatform: vi.fn(),\n publish: vi.fn(),\n pushMcp: vi.fn(),\n },\n prompts: {\n list: vi.fn(),\n get: vi.fn(),\n create: vi.fn(),\n update: vi.fn(),\n delete: vi.fn(),\n getVersions: vi.fn(),\n revert: vi.fn(),\n resolve: vi.fn(),\n apply: vi.fn(),\n },\n };\n}\n\n/**\n * Prime the (already-mocked) configStore with default \"logged in\" values.\n * Each test file must declare `vi.mock('../utils/config-store', ...)` at the\n * top — this helper then sets the common authenticated defaults.\n */\nexport function setupAuthenticatedConfigStore(configStore: {\n getApiKey: Mock;\n getWorkspaceId: Mock;\n isAuthenticated: Mock;\n}) {\n configStore.getApiKey.mockReturnValue('test-api-key');\n configStore.getWorkspaceId.mockReturnValue('ws_123');\n configStore.isAuthenticated.mockReturnValue(true);\n}\n\n// ---------------------------------------------------------------------------\n// Shared mock data\n// ---------------------------------------------------------------------------\n\n// Mock tool data\nexport const mockTool: Tool = {\n id: 'tool_weather123',\n workspaceId: 'ws_123',\n name: 'get_weather',\n description: 'Get weather for a city',\n type: 'http',\n inputSchema: {\n type: 'object',\n properties: {\n city: { type: 'string', description: 'City name' },\n },\n required: ['city'],\n },\n configuration: {\n http: { method: 'GET', url: 'https://wttr.in/{{city}}' },\n },\n isEnabled: true,\n tags: ['weather', 'utility'],\n createdBy: 'user_123',\n stats: {\n totalCalls: 100,\n successfulCalls: 95,\n failedCalls: 5,\n averageLatencyMs: 250,\n lastCalledAt: '2025-01-15T10:30:00Z',\n },\n createdAt: '2025-01-01T00:00:00Z',\n updatedAt: '2025-01-15T10:30:00Z',\n};\n\nexport const mockExecution: ToolExecution = {\n id: 'exec_abc123',\n toolId: 'tool_weather123',\n workspaceId: 'ws_123',\n source: 'api',\n status: 'completed',\n input: { city: 'London' },\n output: { temperature: '12°C', condition: 'Cloudy' },\n executionTimeMs: 245,\n createdAt: '2025-01-15T10:30:00Z',\n updatedAt: '2025-01-15T10:30:00Z',\n};\n\n// Mock MCP config data\nexport const mockMcpConfig = {\n serverUrl: 'https://my-workspace.chanl.app/mcp',\n environment: 'production' as const,\n status: 'active' as const,\n toolCount: 15,\n resourceCount: 8,\n promptCount: 5,\n agentConfigs: {\n claude: {\n mcpServers: {\n chanl: {\n url: 'https://my-workspace.chanl.app/mcp',\n headers: { 'X-API-Key': '${CHANL_MCP_API_KEY}' },\n transport: 'streamable-http',\n },\n },\n },\n cursor: {\n mcpServers: {\n chanl: {\n url: 'https://my-workspace.chanl.app/mcp',\n headers: { 'X-API-Key': '${CHANL_MCP_API_KEY}' },\n transport: 'streamable-http',\n },\n },\n },\n generic: {\n url: 'https://my-workspace.chanl.app/mcp',\n headers: { 'X-API-Key': '${CHANL_MCP_API_KEY}' },\n transport: 'streamable-http',\n },\n },\n};\n\nexport const mockMcpStatusHealthy = {\n healthy: true,\n message: 'MCP server is running',\n responseTimeMs: 45,\n};\n\nexport const mockMcpStatusUnhealthy = {\n healthy: false,\n message: 'Failed to connect to MCP server',\n error: 'Connection refused',\n};\n\n// Mock toolset data\nexport const mockToolset: Toolset = {\n id: 'toolset_abc123',\n workspaceId: 'ws_123',\n name: 'customer_support_tools',\n description: 'Tools for customer support agents',\n tools: ['tool_weather123', 'tool_lookup456'],\n toolCount: 2,\n enabled: true,\n isPublic: false,\n version: '1.0.0',\n tags: ['support', 'customer-facing'],\n createdBy: 'user_123',\n createdAt: '2025-01-01T00:00:00Z',\n updatedAt: '2025-01-15T10:30:00Z',\n};\n\n// ===========================================================================\n// Fetch-level mocking helpers\n// ===========================================================================\n//\n// For tests that want to exercise the real ChanlSDK → fetch pipeline. Use\n// these INSTEAD of makeMockSdk() when writing new tests that should catch\n// CLI↔SDK contract drift.\n//\n// Pattern:\n// vi.mock('../utils/config-store', () => ({\n// configStore: { getBaseUrl: vi.fn(() => 'https://api.test.com'), ... }\n// }));\n// // DO NOT mock '@chanl/sdk' — let the real SDK run.\n//\n// let mockFetch: Mock;\n// beforeEach(() => {\n// mockFetch = installFetchMock();\n// setupFetchConfigStore(configStore as unknown as FetchConfigStoreMock);\n// });\n//\n// it('tools list → GET /api/v1/tools', async () => {\n// mockFetchResponse(mockFetch, {\n// success: true,\n// data: { tools: [mockTool], total: 1 },\n// message: 'OK',\n// timestamp: new Date().toISOString(),\n// });\n//\n// await runCli(['tools', 'list']);\n//\n// expectFetchCalled(mockFetch, {\n// method: 'GET',\n// path: '/api/v1/tools',\n// });\n// });\n\n/** Install a fresh fetch mock on globalThis. Returns the mock for chaining. */\nexport function installFetchMock(): Mock {\n const mockFetch = vi.fn();\n global.fetch = mockFetch as unknown as typeof fetch;\n return mockFetch;\n}\n\n/**\n * Queue a successful JSON response on the fetch mock. Each call queues one\n * response — tests that make N requests need N calls to this in order.\n */\nexport function mockFetchResponse(\n mockFetch: Mock,\n body: unknown,\n status = 200,\n): void {\n mockFetch.mockResolvedValueOnce(\n new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json' },\n }),\n );\n}\n\n/** Queue a JSON error response. SDK reads error.message / message from body. */\nexport function mockFetchError(\n mockFetch: Mock,\n body: unknown,\n status: number,\n): void {\n mockFetch.mockResolvedValueOnce(\n new Response(JSON.stringify(body), {\n status,\n statusText: `Error ${status}`,\n headers: { 'content-type': 'application/json' },\n }),\n );\n}\n\n/**\n * Assert that the Nth fetch call matched the expected method/path and,\n * optionally, body/headers.\n */\nexport function expectFetchCalled(\n mockFetch: Mock,\n expected: {\n method: string;\n path: string;\n body?: unknown;\n headers?: Record<string, string>;\n },\n callIndex = 0,\n): void {\n const calls = mockFetch.mock.calls;\n expect(calls.length).toBeGreaterThan(callIndex);\n const [url, options] = calls[callIndex] as [string, RequestInit];\n expect(url).toContain(expected.path);\n expect((options.method ?? 'GET').toUpperCase()).toBe(\n expected.method.toUpperCase(),\n );\n\n if (expected.body !== undefined) {\n if (typeof options.body === 'string') {\n expect(JSON.parse(options.body)).toEqual(expected.body);\n } else {\n expect(options.body).toEqual(expected.body);\n }\n }\n\n if (expected.headers) {\n const actualHeaders = options.headers as Record<string, string>;\n for (const [k, v] of Object.entries(expected.headers)) {\n expect(actualHeaders[k]).toBe(v);\n }\n }\n}\n\n/** Get the URL string of the Nth fetch call. */\nexport function getFetchUrl(mockFetch: Mock, callIndex = 0): string {\n const call = mockFetch.mock.calls[callIndex] as [string, RequestInit];\n return call[0];\n}\n\n/** Get the parsed JSON body of the Nth fetch call. */\nexport function getFetchBody(mockFetch: Mock, callIndex = 0): unknown {\n const call = mockFetch.mock.calls[callIndex] as [string, RequestInit];\n const body = call[1]?.body;\n if (typeof body === 'string') return JSON.parse(body);\n return body;\n}\n\n/**\n * Type alias for the subset of configStore mock surface fetch-level tests\n * need. Extends the basic \"authenticated\" shape with getBaseUrl, which is\n * critical — the real SDK reads baseUrl from here to build full URLs.\n */\nexport type FetchConfigStoreMock = {\n getApiKey: Mock;\n getBaseUrl: Mock;\n getWorkspaceId: Mock;\n getJwtToken: Mock;\n getDeployment: Mock;\n getApiPrefix: Mock;\n isAuthenticated: Mock;\n};\n\n/**\n * Prime a config store mock for fetch-level tests. Sets a test base URL so\n * the real SDK builds URLs that can be string-matched in assertions.\n */\nexport function setupFetchConfigStore(configStore: FetchConfigStoreMock): void {\n configStore.getApiKey.mockReturnValue('test-api-key');\n configStore.getBaseUrl.mockReturnValue('https://api.test.com');\n configStore.getWorkspaceId.mockReturnValue('ws_123');\n configStore.getJwtToken.mockReturnValue(null);\n configStore.getDeployment.mockReturnValue('cloud');\n configStore.getApiPrefix.mockReturnValue('/api/v1');\n configStore.isAuthenticated.mockReturnValue(true);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { createProgram } from "../cli";
|
|
3
|
+
import { configStore } from "../utils/config-store";
|
|
4
|
+
import { ChanlSDK } from "@chanl/sdk";
|
|
5
|
+
import {
|
|
6
|
+
runCli,
|
|
7
|
+
makeMockSdk,
|
|
8
|
+
setupAuthenticatedConfigStore,
|
|
9
|
+
mockMcpConfig,
|
|
10
|
+
mockMcpStatusHealthy,
|
|
11
|
+
mockMcpStatusUnhealthy
|
|
12
|
+
} from "./helpers";
|
|
13
|
+
vi.mock("@chanl/sdk", () => ({
|
|
14
|
+
ChanlSDK: vi.fn()
|
|
15
|
+
}));
|
|
16
|
+
vi.mock("../utils/config-store", () => ({
|
|
17
|
+
configStore: {
|
|
18
|
+
getApiKey: vi.fn(),
|
|
19
|
+
getBaseUrl: vi.fn(() => "https://api.chanl.ai"),
|
|
20
|
+
getWorkspaceId: vi.fn(() => "ws_123"),
|
|
21
|
+
getJwtToken: vi.fn(() => null),
|
|
22
|
+
getAuthMethod: vi.fn(() => null),
|
|
23
|
+
getAppUrl: vi.fn(() => "https://app.chanl.ai"),
|
|
24
|
+
getDeployment: vi.fn(() => "cloud"),
|
|
25
|
+
getApiPrefix: vi.fn(() => "/api/v1"),
|
|
26
|
+
setApiKey: vi.fn(),
|
|
27
|
+
setBaseUrl: vi.fn(),
|
|
28
|
+
setWorkspaceId: vi.fn(),
|
|
29
|
+
removeApiKey: vi.fn(),
|
|
30
|
+
clearAuth: vi.fn(),
|
|
31
|
+
delete: vi.fn(),
|
|
32
|
+
isAuthenticated: vi.fn(),
|
|
33
|
+
getPath: vi.fn(() => "/home/user/.chanl/config.json")
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
describe("CLI", () => {
|
|
37
|
+
let mockSdk;
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
vi.clearAllMocks();
|
|
40
|
+
mockSdk = makeMockSdk();
|
|
41
|
+
ChanlSDK.mockImplementation(() => mockSdk);
|
|
42
|
+
setupAuthenticatedConfigStore(configStore);
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
process.exitCode = void 0;
|
|
46
|
+
});
|
|
47
|
+
describe("mcp info", () => {
|
|
48
|
+
it("should display MCP info with table output", async () => {
|
|
49
|
+
mockSdk.mcp.getConfig.mockResolvedValue({
|
|
50
|
+
success: true,
|
|
51
|
+
data: mockMcpConfig,
|
|
52
|
+
message: "Success",
|
|
53
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
54
|
+
});
|
|
55
|
+
const { logs, exitCode } = await runCli(["mcp", "info"]);
|
|
56
|
+
expect(exitCode).toBe(0);
|
|
57
|
+
expect(mockSdk.mcp.getConfig).toHaveBeenCalled();
|
|
58
|
+
const output = logs.join("\n");
|
|
59
|
+
expect(output).toContain("https://my-workspace.chanl.app/mcp");
|
|
60
|
+
expect(output).toContain("production");
|
|
61
|
+
expect(output).toContain("15");
|
|
62
|
+
});
|
|
63
|
+
it("should display MCP info with JSON output", async () => {
|
|
64
|
+
mockSdk.mcp.getConfig.mockResolvedValue({
|
|
65
|
+
success: true,
|
|
66
|
+
data: mockMcpConfig,
|
|
67
|
+
message: "Success",
|
|
68
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
69
|
+
});
|
|
70
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "info"]);
|
|
71
|
+
expect(exitCode).toBe(0);
|
|
72
|
+
const output = logs.join("\n");
|
|
73
|
+
const parsed = JSON.parse(output);
|
|
74
|
+
expect(parsed.serverUrl).toBe("https://my-workspace.chanl.app/mcp");
|
|
75
|
+
expect(parsed.environment).toBe("production");
|
|
76
|
+
expect(parsed.toolCount).toBe(15);
|
|
77
|
+
});
|
|
78
|
+
it("should handle API errors", async () => {
|
|
79
|
+
mockSdk.mcp.getConfig.mockRejectedValue(new Error("Network error"));
|
|
80
|
+
const { exitCode, errors } = await runCli(["mcp", "info"]);
|
|
81
|
+
expect(exitCode).toBe(1);
|
|
82
|
+
expect(errors.some((e) => e.includes("Network error"))).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe("mcp status", () => {
|
|
86
|
+
it("should show healthy status", async () => {
|
|
87
|
+
mockSdk.mcp.getStatus.mockResolvedValue({
|
|
88
|
+
success: true,
|
|
89
|
+
data: mockMcpStatusHealthy,
|
|
90
|
+
message: "Success",
|
|
91
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
92
|
+
});
|
|
93
|
+
const { logs, exitCode } = await runCli(["mcp", "status"]);
|
|
94
|
+
expect(exitCode).toBe(0);
|
|
95
|
+
expect(mockSdk.mcp.getStatus).toHaveBeenCalled();
|
|
96
|
+
const output = logs.join("\n");
|
|
97
|
+
expect(output).toContain("healthy");
|
|
98
|
+
expect(output).toContain("MCP server is running");
|
|
99
|
+
});
|
|
100
|
+
it("should show unhealthy status with exit code 1", async () => {
|
|
101
|
+
mockSdk.mcp.getStatus.mockResolvedValue({
|
|
102
|
+
success: true,
|
|
103
|
+
data: mockMcpStatusUnhealthy,
|
|
104
|
+
message: "Success",
|
|
105
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
106
|
+
});
|
|
107
|
+
const { errors, exitCode } = await runCli(["mcp", "status"]);
|
|
108
|
+
expect(exitCode).toBe(1);
|
|
109
|
+
const output = errors.join("\n");
|
|
110
|
+
expect(output).toContain("unhealthy");
|
|
111
|
+
});
|
|
112
|
+
it("should output JSON when --json flag is used", async () => {
|
|
113
|
+
mockSdk.mcp.getStatus.mockResolvedValue({
|
|
114
|
+
success: true,
|
|
115
|
+
data: mockMcpStatusHealthy,
|
|
116
|
+
message: "Success",
|
|
117
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
118
|
+
});
|
|
119
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "status"]);
|
|
120
|
+
expect(exitCode).toBe(0);
|
|
121
|
+
const output = logs.join("\n");
|
|
122
|
+
const parsed = JSON.parse(output);
|
|
123
|
+
expect(parsed.healthy).toBe(true);
|
|
124
|
+
expect(parsed.message).toBe("MCP server is running");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe("mcp config", () => {
|
|
128
|
+
it("should output generic config by default", async () => {
|
|
129
|
+
mockSdk.mcp.getConfig.mockResolvedValue({
|
|
130
|
+
success: true,
|
|
131
|
+
data: mockMcpConfig,
|
|
132
|
+
message: "Success",
|
|
133
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
134
|
+
});
|
|
135
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "config"]);
|
|
136
|
+
expect(exitCode).toBe(0);
|
|
137
|
+
const output = logs.join("\n");
|
|
138
|
+
const parsed = JSON.parse(output);
|
|
139
|
+
expect(parsed.url).toBe("https://my-workspace.chanl.app/mcp");
|
|
140
|
+
expect(parsed.transport).toBe("streamable-http");
|
|
141
|
+
});
|
|
142
|
+
it("should output claude config with --format claude", async () => {
|
|
143
|
+
mockSdk.mcp.getConfig.mockResolvedValue({
|
|
144
|
+
success: true,
|
|
145
|
+
data: mockMcpConfig,
|
|
146
|
+
message: "Success",
|
|
147
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
148
|
+
});
|
|
149
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "config", "--format", "claude"]);
|
|
150
|
+
expect(exitCode).toBe(0);
|
|
151
|
+
const output = logs.join("\n");
|
|
152
|
+
const parsed = JSON.parse(output);
|
|
153
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
154
|
+
expect(parsed.mcpServers.chanl.url).toBe("https://my-workspace.chanl.app/mcp");
|
|
155
|
+
});
|
|
156
|
+
it("should output cursor config with --format cursor", async () => {
|
|
157
|
+
mockSdk.mcp.getConfig.mockResolvedValue({
|
|
158
|
+
success: true,
|
|
159
|
+
data: mockMcpConfig,
|
|
160
|
+
message: "Success",
|
|
161
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
162
|
+
});
|
|
163
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "config", "--format", "cursor"]);
|
|
164
|
+
expect(exitCode).toBe(0);
|
|
165
|
+
const output = logs.join("\n");
|
|
166
|
+
const parsed = JSON.parse(output);
|
|
167
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
168
|
+
expect(parsed.mcpServers.chanl.url).toBe("https://my-workspace.chanl.app/mcp");
|
|
169
|
+
});
|
|
170
|
+
it("should handle API errors", async () => {
|
|
171
|
+
mockSdk.mcp.getConfig.mockRejectedValue(new Error("Network error"));
|
|
172
|
+
const { exitCode, errors } = await runCli(["mcp", "config"]);
|
|
173
|
+
expect(exitCode).toBe(1);
|
|
174
|
+
expect(errors.some((e) => e.includes("Network error"))).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe("mcp command registration", () => {
|
|
178
|
+
it("should have mcp command registered", () => {
|
|
179
|
+
const program = createProgram();
|
|
180
|
+
const commandNames = program.commands.map((c) => c.name());
|
|
181
|
+
expect(commandNames).toContain("mcp");
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
describe("mcp tools", () => {
|
|
185
|
+
const mockMcpTools = [
|
|
186
|
+
{
|
|
187
|
+
name: "get_weather",
|
|
188
|
+
description: "Get current weather for a location",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
city: { type: "string", description: "City name" }
|
|
193
|
+
},
|
|
194
|
+
required: ["city"]
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "search_kb",
|
|
199
|
+
description: "Search the knowledge base",
|
|
200
|
+
inputSchema: {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
query: { type: "string", description: "Search query" }
|
|
204
|
+
},
|
|
205
|
+
required: ["query"]
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
];
|
|
209
|
+
it("should list MCP tools for an agent with table output", async () => {
|
|
210
|
+
mockSdk.mcp.listTools.mockResolvedValue({
|
|
211
|
+
success: true,
|
|
212
|
+
data: { tools: mockMcpTools },
|
|
213
|
+
message: "Success",
|
|
214
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
215
|
+
});
|
|
216
|
+
const { logs, exitCode } = await runCli(["mcp", "tools", "agent_123"]);
|
|
217
|
+
expect(exitCode).toBe(0);
|
|
218
|
+
expect(mockSdk.mcp.listTools).toHaveBeenCalledWith("agent_123");
|
|
219
|
+
const output = logs.join("\n");
|
|
220
|
+
expect(output).toContain("get_weather");
|
|
221
|
+
expect(output).toContain("search_kb");
|
|
222
|
+
});
|
|
223
|
+
it("should list MCP tools with JSON output", async () => {
|
|
224
|
+
mockSdk.mcp.listTools.mockResolvedValue({
|
|
225
|
+
success: true,
|
|
226
|
+
data: { tools: mockMcpTools },
|
|
227
|
+
message: "Success",
|
|
228
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
229
|
+
});
|
|
230
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "tools", "agent_123"]);
|
|
231
|
+
expect(exitCode).toBe(0);
|
|
232
|
+
const output = logs.join("\n");
|
|
233
|
+
const parsed = JSON.parse(output);
|
|
234
|
+
expect(parsed.tools).toHaveLength(2);
|
|
235
|
+
expect(parsed.tools[0].name).toBe("get_weather");
|
|
236
|
+
});
|
|
237
|
+
it("should show message when agent has no tools", async () => {
|
|
238
|
+
mockSdk.mcp.listTools.mockResolvedValue({
|
|
239
|
+
success: true,
|
|
240
|
+
data: { tools: [] },
|
|
241
|
+
message: "Success",
|
|
242
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
243
|
+
});
|
|
244
|
+
const { logs, exitCode } = await runCli(["mcp", "tools", "agent_123"]);
|
|
245
|
+
expect(exitCode).toBe(0);
|
|
246
|
+
const output = logs.join("\n");
|
|
247
|
+
expect(output).toContain("No MCP tools");
|
|
248
|
+
});
|
|
249
|
+
it("should handle API errors", async () => {
|
|
250
|
+
mockSdk.mcp.listTools.mockRejectedValue(new Error("Agent not found"));
|
|
251
|
+
const { exitCode, errors } = await runCli(["mcp", "tools", "agent_123"]);
|
|
252
|
+
expect(exitCode).toBe(1);
|
|
253
|
+
expect(errors.some((e) => e.includes("Agent not found"))).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
describe("mcp call", () => {
|
|
257
|
+
it("should call a tool and display result", async () => {
|
|
258
|
+
mockSdk.mcp.callTool.mockResolvedValue({
|
|
259
|
+
success: true,
|
|
260
|
+
data: {
|
|
261
|
+
content: [{ type: "text", text: "Temperature in London: 15C" }],
|
|
262
|
+
isError: false
|
|
263
|
+
},
|
|
264
|
+
message: "Success",
|
|
265
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
266
|
+
});
|
|
267
|
+
const { logs, exitCode } = await runCli([
|
|
268
|
+
"mcp",
|
|
269
|
+
"call",
|
|
270
|
+
"agent_123",
|
|
271
|
+
"get_weather",
|
|
272
|
+
'{"city":"London"}'
|
|
273
|
+
]);
|
|
274
|
+
expect(exitCode).toBe(0);
|
|
275
|
+
expect(mockSdk.mcp.callTool).toHaveBeenCalledWith(
|
|
276
|
+
"agent_123",
|
|
277
|
+
"get_weather",
|
|
278
|
+
{ city: "London" }
|
|
279
|
+
);
|
|
280
|
+
const output = logs.join("\n");
|
|
281
|
+
expect(output).toContain("Temperature in London: 15C");
|
|
282
|
+
});
|
|
283
|
+
it("should call a tool without arguments", async () => {
|
|
284
|
+
mockSdk.mcp.callTool.mockResolvedValue({
|
|
285
|
+
success: true,
|
|
286
|
+
data: {
|
|
287
|
+
content: [{ type: "text", text: "pong" }],
|
|
288
|
+
isError: false
|
|
289
|
+
},
|
|
290
|
+
message: "Success",
|
|
291
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
292
|
+
});
|
|
293
|
+
const { logs, exitCode } = await runCli(["mcp", "call", "agent_123", "ping"]);
|
|
294
|
+
expect(exitCode).toBe(0);
|
|
295
|
+
expect(mockSdk.mcp.callTool).toHaveBeenCalledWith(
|
|
296
|
+
"agent_123",
|
|
297
|
+
"ping",
|
|
298
|
+
void 0
|
|
299
|
+
);
|
|
300
|
+
const output = logs.join("\n");
|
|
301
|
+
expect(output).toContain("pong");
|
|
302
|
+
});
|
|
303
|
+
it("should display error result from tool", async () => {
|
|
304
|
+
mockSdk.mcp.callTool.mockResolvedValue({
|
|
305
|
+
success: true,
|
|
306
|
+
data: {
|
|
307
|
+
content: [{ type: "text", text: "Error: city not found" }],
|
|
308
|
+
isError: true
|
|
309
|
+
},
|
|
310
|
+
message: "Success",
|
|
311
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
312
|
+
});
|
|
313
|
+
const { errors, exitCode } = await runCli([
|
|
314
|
+
"mcp",
|
|
315
|
+
"call",
|
|
316
|
+
"agent_123",
|
|
317
|
+
"get_weather",
|
|
318
|
+
'{"city":"xyz"}'
|
|
319
|
+
]);
|
|
320
|
+
expect(exitCode).toBe(1);
|
|
321
|
+
const output = errors.join("\n");
|
|
322
|
+
expect(output).toContain("Error");
|
|
323
|
+
});
|
|
324
|
+
it("should output JSON when --json flag is used", async () => {
|
|
325
|
+
mockSdk.mcp.callTool.mockResolvedValue({
|
|
326
|
+
success: true,
|
|
327
|
+
data: {
|
|
328
|
+
content: [{ type: "text", text: "result text" }],
|
|
329
|
+
isError: false
|
|
330
|
+
},
|
|
331
|
+
message: "Success",
|
|
332
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
333
|
+
});
|
|
334
|
+
const { logs, exitCode } = await runCli([
|
|
335
|
+
"--json",
|
|
336
|
+
"mcp",
|
|
337
|
+
"call",
|
|
338
|
+
"agent_123",
|
|
339
|
+
"get_weather",
|
|
340
|
+
'{"city":"London"}'
|
|
341
|
+
]);
|
|
342
|
+
expect(exitCode).toBe(0);
|
|
343
|
+
const output = logs.join("\n");
|
|
344
|
+
const parsed = JSON.parse(output);
|
|
345
|
+
expect(parsed.content).toBeDefined();
|
|
346
|
+
expect(parsed.content[0].text).toBe("result text");
|
|
347
|
+
});
|
|
348
|
+
it("should handle invalid JSON arguments gracefully", async () => {
|
|
349
|
+
const { exitCode, errors } = await runCli([
|
|
350
|
+
"mcp",
|
|
351
|
+
"call",
|
|
352
|
+
"agent_123",
|
|
353
|
+
"get_weather",
|
|
354
|
+
"not-json"
|
|
355
|
+
]);
|
|
356
|
+
expect(exitCode).toBe(1);
|
|
357
|
+
expect(errors.some((e) => e.includes("Invalid JSON"))).toBe(true);
|
|
358
|
+
});
|
|
359
|
+
it("should handle API errors", async () => {
|
|
360
|
+
mockSdk.mcp.callTool.mockRejectedValue(new Error("Tool not found"));
|
|
361
|
+
const { exitCode, errors } = await runCli([
|
|
362
|
+
"mcp",
|
|
363
|
+
"call",
|
|
364
|
+
"agent_123",
|
|
365
|
+
"nonexistent",
|
|
366
|
+
"{}"
|
|
367
|
+
]);
|
|
368
|
+
expect(exitCode).toBe(1);
|
|
369
|
+
expect(errors.some((e) => e.includes("Tool not found"))).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
describe("mcp inspect", () => {
|
|
373
|
+
const mockToolDetail = {
|
|
374
|
+
name: "get_weather",
|
|
375
|
+
description: "Get current weather for a location",
|
|
376
|
+
inputSchema: {
|
|
377
|
+
type: "object",
|
|
378
|
+
properties: {
|
|
379
|
+
city: { type: "string", description: "City name" },
|
|
380
|
+
units: { type: "string", enum: ["celsius", "fahrenheit"] }
|
|
381
|
+
},
|
|
382
|
+
required: ["city"]
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
it("should display tool details with table output", async () => {
|
|
386
|
+
mockSdk.mcp.getTool.mockResolvedValue({
|
|
387
|
+
success: true,
|
|
388
|
+
data: mockToolDetail,
|
|
389
|
+
message: "Success",
|
|
390
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
391
|
+
});
|
|
392
|
+
const { logs, exitCode } = await runCli(["mcp", "inspect", "agent_123", "get_weather"]);
|
|
393
|
+
expect(exitCode).toBe(0);
|
|
394
|
+
expect(mockSdk.mcp.getTool).toHaveBeenCalledWith("agent_123", "get_weather");
|
|
395
|
+
const output = logs.join("\n");
|
|
396
|
+
expect(output).toContain("get_weather");
|
|
397
|
+
expect(output).toContain("Get current weather");
|
|
398
|
+
expect(output).toContain("city");
|
|
399
|
+
});
|
|
400
|
+
it("should display tool details with JSON output", async () => {
|
|
401
|
+
mockSdk.mcp.getTool.mockResolvedValue({
|
|
402
|
+
success: true,
|
|
403
|
+
data: mockToolDetail,
|
|
404
|
+
message: "Success",
|
|
405
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
406
|
+
});
|
|
407
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "inspect", "agent_123", "get_weather"]);
|
|
408
|
+
expect(exitCode).toBe(0);
|
|
409
|
+
const output = logs.join("\n");
|
|
410
|
+
const parsed = JSON.parse(output);
|
|
411
|
+
expect(parsed.name).toBe("get_weather");
|
|
412
|
+
expect(parsed.inputSchema).toBeDefined();
|
|
413
|
+
});
|
|
414
|
+
it("should handle tool not found", async () => {
|
|
415
|
+
mockSdk.mcp.getTool.mockRejectedValue(new Error("Tool not found"));
|
|
416
|
+
const { exitCode, errors } = await runCli(["mcp", "inspect", "agent_123", "nonexistent"]);
|
|
417
|
+
expect(exitCode).toBe(1);
|
|
418
|
+
expect(errors.some((e) => e.includes("Tool not found"))).toBe(true);
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
describe("mcp install", () => {
|
|
422
|
+
it("should generate config snippet for default (generic) format", async () => {
|
|
423
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "install"]);
|
|
424
|
+
expect(exitCode).toBe(0);
|
|
425
|
+
const output = logs.join("\n");
|
|
426
|
+
const parsed = JSON.parse(output);
|
|
427
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
428
|
+
expect(parsed.mcpServers.chanl).toBeDefined();
|
|
429
|
+
expect(parsed.mcpServers.chanl.transport).toBe("streamable-http");
|
|
430
|
+
});
|
|
431
|
+
it("should include API key from config", async () => {
|
|
432
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "install"]);
|
|
433
|
+
expect(exitCode).toBe(0);
|
|
434
|
+
const output = logs.join("\n");
|
|
435
|
+
const parsed = JSON.parse(output);
|
|
436
|
+
expect(parsed.mcpServers.chanl.headers["X-API-Key"]).toBe("test-api-key");
|
|
437
|
+
});
|
|
438
|
+
it("should generate config for claude format", async () => {
|
|
439
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "install", "--format", "claude"]);
|
|
440
|
+
expect(exitCode).toBe(0);
|
|
441
|
+
const output = logs.join("\n");
|
|
442
|
+
const parsed = JSON.parse(output);
|
|
443
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
444
|
+
expect(parsed.mcpServers.chanl).toBeDefined();
|
|
445
|
+
});
|
|
446
|
+
it("should generate config for cursor format", async () => {
|
|
447
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "install", "--format", "cursor"]);
|
|
448
|
+
expect(exitCode).toBe(0);
|
|
449
|
+
const output = logs.join("\n");
|
|
450
|
+
const parsed = JSON.parse(output);
|
|
451
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
452
|
+
expect(parsed.mcpServers.chanl).toBeDefined();
|
|
453
|
+
});
|
|
454
|
+
it("should generate config for vscode format", async () => {
|
|
455
|
+
const { logs, exitCode } = await runCli(["--json", "mcp", "install", "--format", "vscode"]);
|
|
456
|
+
expect(exitCode).toBe(0);
|
|
457
|
+
const output = logs.join("\n");
|
|
458
|
+
const parsed = JSON.parse(output);
|
|
459
|
+
expect(parsed.mcpServers).toBeDefined();
|
|
460
|
+
expect(parsed.mcpServers.chanl).toBeDefined();
|
|
461
|
+
});
|
|
462
|
+
it("should error when not authenticated", async () => {
|
|
463
|
+
configStore.getApiKey.mockReturnValue(null);
|
|
464
|
+
const { exitCode, errors } = await runCli(["mcp", "install"]);
|
|
465
|
+
expect(exitCode).toBe(1);
|
|
466
|
+
expect(errors.some((e) => e.includes("Not authenticated") || e.includes("login"))).toBe(true);
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
//# sourceMappingURL=mcp.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/mcp.test.ts"],"sourcesContent":["/**\n * CLI mcp tests — mcp info/status/config/tools/call/inspect/install + command registration\n */\n\nimport { describe, it, expect, beforeEach, afterEach, vi, Mock } from 'vitest';\nimport { createProgram } from '../cli';\nimport { configStore } from '../utils/config-store';\nimport { ChanlSDK } from '@chanl/sdk';\nimport {\n runCli,\n makeMockSdk,\n setupAuthenticatedConfigStore,\n mockMcpConfig,\n mockMcpStatusHealthy,\n mockMcpStatusUnhealthy,\n type MockSdk,\n} from './helpers';\n\n// Mock the SDK\nvi.mock('@chanl/sdk', () => ({\n ChanlSDK: vi.fn(),\n}));\n\n// Mock config store\nvi.mock('../utils/config-store', () => ({\n configStore: {\n getApiKey: vi.fn(),\n getBaseUrl: vi.fn(() => 'https://api.chanl.ai'),\n getWorkspaceId: vi.fn(() => 'ws_123'),\n getJwtToken: vi.fn(() => null),\n getAuthMethod: vi.fn(() => null),\n getAppUrl: vi.fn(() => 'https://app.chanl.ai'),\n getDeployment: vi.fn(() => 'cloud'),\n getApiPrefix: vi.fn(() => '/api/v1'),\n setApiKey: vi.fn(),\n setBaseUrl: vi.fn(),\n setWorkspaceId: vi.fn(),\n removeApiKey: vi.fn(),\n clearAuth: vi.fn(),\n delete: vi.fn(),\n isAuthenticated: vi.fn(),\n getPath: vi.fn(() => '/home/user/.chanl/config.json'),\n },\n}));\n\ndescribe('CLI', () => {\n let mockSdk: MockSdk;\n\n beforeEach(() => {\n vi.clearAllMocks();\n mockSdk = makeMockSdk();\n (ChanlSDK as Mock).mockImplementation(() => mockSdk);\n setupAuthenticatedConfigStore(configStore as unknown as {\n getApiKey: Mock;\n getWorkspaceId: Mock;\n isAuthenticated: Mock;\n });\n });\n\n afterEach(() => {\n process.exitCode = undefined;\n });\n\n describe('mcp info', () => {\n it('should display MCP info with table output', async () => {\n mockSdk.mcp.getConfig.mockResolvedValue({\n success: true,\n data: mockMcpConfig,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'info']);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.getConfig).toHaveBeenCalled();\n\n const output = logs.join('\\n');\n expect(output).toContain('https://my-workspace.chanl.app/mcp');\n expect(output).toContain('production');\n expect(output).toContain('15'); // toolCount\n });\n\n it('should display MCP info with JSON output', async () => {\n mockSdk.mcp.getConfig.mockResolvedValue({\n success: true,\n data: mockMcpConfig,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'info']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.serverUrl).toBe('https://my-workspace.chanl.app/mcp');\n expect(parsed.environment).toBe('production');\n expect(parsed.toolCount).toBe(15);\n });\n\n it('should handle API errors', async () => {\n mockSdk.mcp.getConfig.mockRejectedValue(new Error('Network error'));\n\n const { exitCode, errors } = await runCli(['mcp', 'info']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e) => e.includes('Network error'))).toBe(true);\n });\n });\n\n describe('mcp status', () => {\n it('should show healthy status', async () => {\n mockSdk.mcp.getStatus.mockResolvedValue({\n success: true,\n data: mockMcpStatusHealthy,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'status']);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.getStatus).toHaveBeenCalled();\n\n const output = logs.join('\\n');\n expect(output).toContain('healthy');\n expect(output).toContain('MCP server is running');\n });\n\n it('should show unhealthy status with exit code 1', async () => {\n mockSdk.mcp.getStatus.mockResolvedValue({\n success: true,\n data: mockMcpStatusUnhealthy,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { errors, exitCode } = await runCli(['mcp', 'status']);\n\n expect(exitCode).toBe(1);\n\n const output = errors.join('\\n');\n expect(output).toContain('unhealthy');\n });\n\n it('should output JSON when --json flag is used', async () => {\n mockSdk.mcp.getStatus.mockResolvedValue({\n success: true,\n data: mockMcpStatusHealthy,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'status']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.healthy).toBe(true);\n expect(parsed.message).toBe('MCP server is running');\n });\n });\n\n describe('mcp config', () => {\n it('should output generic config by default', async () => {\n mockSdk.mcp.getConfig.mockResolvedValue({\n success: true,\n data: mockMcpConfig,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n // Use --json to avoid extra info messages in output\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'config']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.url).toBe('https://my-workspace.chanl.app/mcp');\n expect(parsed.transport).toBe('streamable-http');\n });\n\n it('should output claude config with --format claude', async () => {\n mockSdk.mcp.getConfig.mockResolvedValue({\n success: true,\n data: mockMcpConfig,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n // Use --json to avoid extra info messages in output\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'config', '--format', 'claude']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl.url).toBe('https://my-workspace.chanl.app/mcp');\n });\n\n it('should output cursor config with --format cursor', async () => {\n mockSdk.mcp.getConfig.mockResolvedValue({\n success: true,\n data: mockMcpConfig,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n // Use --json to avoid extra info messages in output\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'config', '--format', 'cursor']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl.url).toBe('https://my-workspace.chanl.app/mcp');\n });\n\n it('should handle API errors', async () => {\n mockSdk.mcp.getConfig.mockRejectedValue(new Error('Network error'));\n\n const { exitCode, errors } = await runCli(['mcp', 'config']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e) => e.includes('Network error'))).toBe(true);\n });\n });\n\n describe('mcp command registration', () => {\n it('should have mcp command registered', () => {\n const program = createProgram();\n const commandNames = program.commands.map((c) => c.name());\n\n expect(commandNames).toContain('mcp');\n });\n });\n\n // ============================================\n // MCP Tools Commands (Story 10)\n // ============================================\n\n describe('mcp tools', () => {\n const mockMcpTools = [\n {\n name: 'get_weather',\n description: 'Get current weather for a location',\n inputSchema: {\n type: 'object',\n properties: {\n city: { type: 'string', description: 'City name' },\n },\n required: ['city'],\n },\n },\n {\n name: 'search_kb',\n description: 'Search the knowledge base',\n inputSchema: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Search query' },\n },\n required: ['query'],\n },\n },\n ];\n\n it('should list MCP tools for an agent with table output', async () => {\n mockSdk.mcp.listTools.mockResolvedValue({\n success: true,\n data: { tools: mockMcpTools },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'tools', 'agent_123']);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.listTools).toHaveBeenCalledWith('agent_123');\n\n const output = logs.join('\\n');\n expect(output).toContain('get_weather');\n expect(output).toContain('search_kb');\n });\n\n it('should list MCP tools with JSON output', async () => {\n mockSdk.mcp.listTools.mockResolvedValue({\n success: true,\n data: { tools: mockMcpTools },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'tools', 'agent_123']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.tools).toHaveLength(2);\n expect(parsed.tools[0].name).toBe('get_weather');\n });\n\n it('should show message when agent has no tools', async () => {\n mockSdk.mcp.listTools.mockResolvedValue({\n success: true,\n data: { tools: [] },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'tools', 'agent_123']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n expect(output).toContain('No MCP tools');\n });\n\n it('should handle API errors', async () => {\n mockSdk.mcp.listTools.mockRejectedValue(new Error('Agent not found'));\n\n const { exitCode, errors } = await runCli(['mcp', 'tools', 'agent_123']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e: string) => e.includes('Agent not found'))).toBe(true);\n });\n });\n\n describe('mcp call', () => {\n it('should call a tool and display result', async () => {\n mockSdk.mcp.callTool.mockResolvedValue({\n success: true,\n data: {\n content: [{ type: 'text', text: 'Temperature in London: 15C' }],\n isError: false,\n },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli([\n 'mcp', 'call', 'agent_123', 'get_weather', '{\"city\":\"London\"}',\n ]);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.callTool).toHaveBeenCalledWith(\n 'agent_123', 'get_weather', { city: 'London' }\n );\n\n const output = logs.join('\\n');\n expect(output).toContain('Temperature in London: 15C');\n });\n\n it('should call a tool without arguments', async () => {\n mockSdk.mcp.callTool.mockResolvedValue({\n success: true,\n data: {\n content: [{ type: 'text', text: 'pong' }],\n isError: false,\n },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'call', 'agent_123', 'ping']);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.callTool).toHaveBeenCalledWith(\n 'agent_123', 'ping', undefined\n );\n\n const output = logs.join('\\n');\n expect(output).toContain('pong');\n });\n\n it('should display error result from tool', async () => {\n mockSdk.mcp.callTool.mockResolvedValue({\n success: true,\n data: {\n content: [{ type: 'text', text: 'Error: city not found' }],\n isError: true,\n },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { errors, exitCode } = await runCli([\n 'mcp', 'call', 'agent_123', 'get_weather', '{\"city\":\"xyz\"}',\n ]);\n\n expect(exitCode).toBe(1);\n const output = errors.join('\\n');\n expect(output).toContain('Error');\n });\n\n it('should output JSON when --json flag is used', async () => {\n mockSdk.mcp.callTool.mockResolvedValue({\n success: true,\n data: {\n content: [{ type: 'text', text: 'result text' }],\n isError: false,\n },\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli([\n '--json', 'mcp', 'call', 'agent_123', 'get_weather', '{\"city\":\"London\"}',\n ]);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.content).toBeDefined();\n expect(parsed.content[0].text).toBe('result text');\n });\n\n it('should handle invalid JSON arguments gracefully', async () => {\n const { exitCode, errors } = await runCli([\n 'mcp', 'call', 'agent_123', 'get_weather', 'not-json',\n ]);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e: string) => e.includes('Invalid JSON'))).toBe(true);\n });\n\n it('should handle API errors', async () => {\n mockSdk.mcp.callTool.mockRejectedValue(new Error('Tool not found'));\n\n const { exitCode, errors } = await runCli([\n 'mcp', 'call', 'agent_123', 'nonexistent', '{}',\n ]);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e: string) => e.includes('Tool not found'))).toBe(true);\n });\n });\n\n describe('mcp inspect', () => {\n const mockToolDetail = {\n name: 'get_weather',\n description: 'Get current weather for a location',\n inputSchema: {\n type: 'object',\n properties: {\n city: { type: 'string', description: 'City name' },\n units: { type: 'string', enum: ['celsius', 'fahrenheit'] },\n },\n required: ['city'],\n },\n };\n\n it('should display tool details with table output', async () => {\n mockSdk.mcp.getTool.mockResolvedValue({\n success: true,\n data: mockToolDetail,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['mcp', 'inspect', 'agent_123', 'get_weather']);\n\n expect(exitCode).toBe(0);\n expect(mockSdk.mcp.getTool).toHaveBeenCalledWith('agent_123', 'get_weather');\n\n const output = logs.join('\\n');\n expect(output).toContain('get_weather');\n expect(output).toContain('Get current weather');\n expect(output).toContain('city');\n });\n\n it('should display tool details with JSON output', async () => {\n mockSdk.mcp.getTool.mockResolvedValue({\n success: true,\n data: mockToolDetail,\n message: 'Success',\n timestamp: new Date().toISOString(),\n });\n\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'inspect', 'agent_123', 'get_weather']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.name).toBe('get_weather');\n expect(parsed.inputSchema).toBeDefined();\n });\n\n it('should handle tool not found', async () => {\n mockSdk.mcp.getTool.mockRejectedValue(new Error('Tool not found'));\n\n const { exitCode, errors } = await runCli(['mcp', 'inspect', 'agent_123', 'nonexistent']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e: string) => e.includes('Tool not found'))).toBe(true);\n });\n });\n\n describe('mcp install', () => {\n it('should generate config snippet for default (generic) format', async () => {\n // Use --json to suppress info messages in output (config is always JSON)\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'install']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl).toBeDefined();\n expect(parsed.mcpServers.chanl.transport).toBe('streamable-http');\n });\n\n it('should include API key from config', async () => {\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'install']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers.chanl.headers['X-API-Key']).toBe('test-api-key');\n });\n\n it('should generate config for claude format', async () => {\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'install', '--format', 'claude']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl).toBeDefined();\n });\n\n it('should generate config for cursor format', async () => {\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'install', '--format', 'cursor']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl).toBeDefined();\n });\n\n it('should generate config for vscode format', async () => {\n const { logs, exitCode } = await runCli(['--json', 'mcp', 'install', '--format', 'vscode']);\n\n expect(exitCode).toBe(0);\n\n const output = logs.join('\\n');\n const parsed = JSON.parse(output);\n expect(parsed.mcpServers).toBeDefined();\n expect(parsed.mcpServers.chanl).toBeDefined();\n });\n\n it('should error when not authenticated', async () => {\n (configStore.getApiKey as Mock).mockReturnValue(null);\n\n const { exitCode, errors } = await runCli(['mcp', 'install']);\n\n expect(exitCode).toBe(1);\n expect(errors.some((e: string) => e.includes('Not authenticated') || e.includes('login'))).toBe(true);\n });\n });\n});\n"],"mappings":"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","names":[]}
|