mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
test: add comprehensive tests for MCP prompts functionality
- Test getMcpPromptsAsCommands for converting prompts to commands - Test executeMcpPrompt for executing prompts with arguments - Test parsePromptArguments for parsing positional arguments - Ensure proper handling of edge cases and errors
This commit is contained in:
parent
e99023a9c7
commit
fe96d0db10
4 changed files with 404 additions and 7 deletions
|
|
@ -53,7 +53,7 @@ describe("Command Mentions", () => {
|
|||
const input = "/setup Please help me set up the project"
|
||||
const result = await callParseMentions(input)
|
||||
|
||||
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "setup")
|
||||
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "setup", undefined)
|
||||
expect(result).toContain('<command name="setup">')
|
||||
expect(result).toContain(commandContent)
|
||||
expect(result).toContain("</command>")
|
||||
|
|
@ -94,8 +94,8 @@ describe("Command Mentions", () => {
|
|||
const input = "/setup the project\nThen /deploy later"
|
||||
const result = await callParseMentions(input)
|
||||
|
||||
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "setup")
|
||||
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "deploy")
|
||||
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "setup", undefined)
|
||||
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "deploy", undefined)
|
||||
expect(mockGetCommand).toHaveBeenCalledTimes(2) // Each unique command called once (optimized)
|
||||
expect(result).toContain('<command name="setup">')
|
||||
expect(result).toContain("# Setup Environment")
|
||||
|
|
@ -110,7 +110,7 @@ describe("Command Mentions", () => {
|
|||
const input = "/nonexistent command"
|
||||
const result = await callParseMentions(input)
|
||||
|
||||
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "nonexistent")
|
||||
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "nonexistent", undefined)
|
||||
// The command should remain unchanged in the text
|
||||
expect(result).toBe("/nonexistent command")
|
||||
// Should not contain any command tags
|
||||
|
|
@ -159,7 +159,7 @@ describe("Command Mentions", () => {
|
|||
const input = "/setup-dev for the project"
|
||||
const result = await callParseMentions(input)
|
||||
|
||||
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "setup-dev")
|
||||
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "setup-dev", undefined)
|
||||
expect(result).toContain('<command name="setup-dev">')
|
||||
expect(result).toContain("# Dev setup")
|
||||
})
|
||||
|
|
|
|||
392
src/services/command/__tests__/mcp-prompts.spec.ts
Normal file
392
src/services/command/__tests__/mcp-prompts.spec.ts
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest"
|
||||
import { getMcpPromptsAsCommands, executeMcpPrompt, parsePromptArguments } from "../mcp-prompts"
|
||||
import { McpHub } from "../../mcp/McpHub"
|
||||
import type { McpServer, McpPrompt } from "../../../shared/mcp"
|
||||
|
||||
// Mock McpHub
|
||||
vi.mock("../../mcp/McpHub")
|
||||
|
||||
describe("MCP Prompts", () => {
|
||||
let mockMcpHub: any
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockMcpHub = {
|
||||
getAllServers: vi.fn(),
|
||||
getPrompt: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe("getMcpPromptsAsCommands", () => {
|
||||
it("should return empty array when mcpHub is undefined", async () => {
|
||||
const result = await getMcpPromptsAsCommands(undefined)
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it("should return empty array when no servers have prompts", async () => {
|
||||
const servers: McpServer[] = [
|
||||
{
|
||||
name: "test-server",
|
||||
config: "test-config",
|
||||
status: "connected",
|
||||
tools: [],
|
||||
resources: [],
|
||||
prompts: [],
|
||||
},
|
||||
]
|
||||
mockMcpHub.getAllServers.mockReturnValue(servers)
|
||||
|
||||
const result = await getMcpPromptsAsCommands(mockMcpHub)
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it("should convert MCP prompts to commands", async () => {
|
||||
const prompts: McpPrompt[] = [
|
||||
{
|
||||
name: "generate-code",
|
||||
description: "Generate code based on requirements",
|
||||
arguments: [
|
||||
{
|
||||
name: "language",
|
||||
description: "Programming language",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "explain",
|
||||
description: "Explain a concept",
|
||||
arguments: [],
|
||||
},
|
||||
]
|
||||
|
||||
const servers: McpServer[] = [
|
||||
{
|
||||
name: "coding-assistant",
|
||||
config: "test-config",
|
||||
status: "connected",
|
||||
tools: [],
|
||||
resources: [],
|
||||
prompts,
|
||||
source: "global",
|
||||
},
|
||||
]
|
||||
mockMcpHub.getAllServers.mockReturnValue(servers)
|
||||
|
||||
const result = await getMcpPromptsAsCommands(mockMcpHub)
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0]).toEqual({
|
||||
name: "mcp.coding-assistant.generate-code",
|
||||
content: "",
|
||||
source: "global",
|
||||
filePath: "",
|
||||
description: "Generate code based on requirements",
|
||||
argumentHint: "<language>",
|
||||
})
|
||||
expect(result[1]).toEqual({
|
||||
name: "mcp.coding-assistant.explain",
|
||||
content: "",
|
||||
source: "global",
|
||||
filePath: "",
|
||||
description: "Explain a concept",
|
||||
argumentHint: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle multiple servers with prompts", async () => {
|
||||
const server1: McpServer = {
|
||||
name: "server1",
|
||||
config: "test-config",
|
||||
status: "connected",
|
||||
tools: [],
|
||||
resources: [],
|
||||
prompts: [
|
||||
{
|
||||
name: "prompt1",
|
||||
description: "First prompt",
|
||||
arguments: [],
|
||||
},
|
||||
],
|
||||
source: "project",
|
||||
}
|
||||
|
||||
const server2: McpServer = {
|
||||
name: "server2",
|
||||
config: "test-config",
|
||||
status: "connected",
|
||||
tools: [],
|
||||
resources: [],
|
||||
prompts: [
|
||||
{
|
||||
name: "prompt2",
|
||||
description: "Second prompt",
|
||||
arguments: [
|
||||
{
|
||||
name: "param",
|
||||
description: "A parameter",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
source: "global",
|
||||
}
|
||||
|
||||
mockMcpHub.getAllServers.mockReturnValue([server1, server2])
|
||||
|
||||
const result = await getMcpPromptsAsCommands(mockMcpHub)
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].name).toBe("mcp.server1.prompt1")
|
||||
expect(result[1].name).toBe("mcp.server2.prompt2")
|
||||
expect(result[1].argumentHint).toBe("[param]")
|
||||
})
|
||||
|
||||
it("should handle prompts with multiple arguments", async () => {
|
||||
const prompts: McpPrompt[] = [
|
||||
{
|
||||
name: "complex-prompt",
|
||||
description: "A complex prompt",
|
||||
arguments: [
|
||||
{
|
||||
name: "arg1",
|
||||
description: "First argument",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "arg2",
|
||||
description: "Second argument",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "arg3",
|
||||
description: "Third argument",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const servers: McpServer[] = [
|
||||
{
|
||||
name: "test",
|
||||
config: "test-config",
|
||||
status: "connected",
|
||||
tools: [],
|
||||
resources: [],
|
||||
prompts,
|
||||
source: "global",
|
||||
},
|
||||
]
|
||||
mockMcpHub.getAllServers.mockReturnValue(servers)
|
||||
|
||||
const result = await getMcpPromptsAsCommands(mockMcpHub)
|
||||
|
||||
expect(result[0].argumentHint).toBe("<arg1> <arg3> [arg2]")
|
||||
})
|
||||
})
|
||||
|
||||
describe("executeMcpPrompt", () => {
|
||||
it("should execute prompt without arguments", async () => {
|
||||
const mockResponse = {
|
||||
messages: [
|
||||
{
|
||||
content: {
|
||||
type: "text" as const,
|
||||
text: "Generated content",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
mockMcpHub.getPrompt.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await executeMcpPrompt(mockMcpHub, "test-server", "test-prompt", {})
|
||||
|
||||
expect(mockMcpHub.getPrompt).toHaveBeenCalledWith("test-server", "test-prompt", {})
|
||||
expect(result).toBe("Generated content")
|
||||
})
|
||||
|
||||
it("should execute prompt with arguments", async () => {
|
||||
const mockResponse = {
|
||||
messages: [
|
||||
{
|
||||
content: {
|
||||
type: "text" as const,
|
||||
text: "Result with args",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
mockMcpHub.getPrompt.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await executeMcpPrompt(mockMcpHub, "server", "prompt", {
|
||||
language: "python",
|
||||
description: "A test function",
|
||||
})
|
||||
|
||||
expect(mockMcpHub.getPrompt).toHaveBeenCalledWith("server", "prompt", {
|
||||
language: "python",
|
||||
description: "A test function",
|
||||
})
|
||||
expect(result).toBe("Result with args")
|
||||
})
|
||||
|
||||
it("should handle multiple messages in response", async () => {
|
||||
const mockResponse = {
|
||||
messages: [
|
||||
{
|
||||
content: {
|
||||
type: "text" as const,
|
||||
text: "First message",
|
||||
},
|
||||
},
|
||||
{
|
||||
content: {
|
||||
type: "text" as const,
|
||||
text: "Second message",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
mockMcpHub.getPrompt.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await executeMcpPrompt(mockMcpHub, "server", "prompt", {})
|
||||
|
||||
expect(result).toBe("First message\n\nSecond message")
|
||||
})
|
||||
|
||||
it("should handle resource content types", async () => {
|
||||
const mockResponse = {
|
||||
messages: [
|
||||
{
|
||||
content: {
|
||||
type: "resource" as const,
|
||||
resource: {
|
||||
text: "Resource content",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
mockMcpHub.getPrompt.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await executeMcpPrompt(mockMcpHub, "server", "prompt", {})
|
||||
|
||||
expect(result).toBe("Resource content")
|
||||
})
|
||||
|
||||
it("should handle errors gracefully", async () => {
|
||||
mockMcpHub.getPrompt.mockRejectedValue(new Error("Connection failed"))
|
||||
|
||||
await expect(executeMcpPrompt(mockMcpHub, "server", "prompt", {})).rejects.toThrow(
|
||||
"Failed to execute MCP prompt: Connection failed",
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle empty response", async () => {
|
||||
const mockResponse = {
|
||||
messages: [],
|
||||
}
|
||||
mockMcpHub.getPrompt.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await executeMcpPrompt(mockMcpHub, "server", "prompt", {})
|
||||
|
||||
expect(result).toBe("No messages returned from MCP prompt")
|
||||
})
|
||||
|
||||
it("should handle messages without text content", async () => {
|
||||
const mockResponse = {
|
||||
messages: [
|
||||
{
|
||||
content: {
|
||||
type: "text" as const,
|
||||
text: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
content: {
|
||||
type: "resource" as const,
|
||||
resource: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
mockMcpHub.getPrompt.mockResolvedValue(mockResponse)
|
||||
|
||||
const result = await executeMcpPrompt(mockMcpHub, "server", "prompt", {})
|
||||
|
||||
expect(result).toBe("No content returned from MCP prompt")
|
||||
})
|
||||
})
|
||||
|
||||
describe("parsePromptArguments", () => {
|
||||
it("should return empty object for prompts without arguments", () => {
|
||||
const prompt: McpPrompt = {
|
||||
name: "test",
|
||||
description: "Test prompt",
|
||||
arguments: [],
|
||||
}
|
||||
|
||||
const result = parsePromptArguments(prompt, "some args")
|
||||
expect(result).toEqual({})
|
||||
})
|
||||
|
||||
it("should parse positional arguments", () => {
|
||||
const prompt: McpPrompt = {
|
||||
name: "test",
|
||||
description: "Test prompt",
|
||||
arguments: [
|
||||
{ name: "language", description: "Language", required: true },
|
||||
{ name: "framework", description: "Framework", required: false },
|
||||
],
|
||||
}
|
||||
|
||||
const result = parsePromptArguments(prompt, "python django")
|
||||
expect(result).toEqual({
|
||||
language: "python",
|
||||
framework: "django",
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle missing optional arguments", () => {
|
||||
const prompt: McpPrompt = {
|
||||
name: "test",
|
||||
description: "Test prompt",
|
||||
arguments: [
|
||||
{ name: "required", description: "Required arg", required: true },
|
||||
{ name: "optional", description: "Optional arg", required: false },
|
||||
],
|
||||
}
|
||||
|
||||
const result = parsePromptArguments(prompt, "value1")
|
||||
expect(result).toEqual({
|
||||
required: "value1",
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle extra arguments", () => {
|
||||
const prompt: McpPrompt = {
|
||||
name: "test",
|
||||
description: "Test prompt",
|
||||
arguments: [{ name: "arg1", description: "Arg 1", required: true }],
|
||||
}
|
||||
|
||||
const result = parsePromptArguments(prompt, "value1 value2 value3")
|
||||
expect(result).toEqual({
|
||||
arg1: "value1",
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle empty argument string", () => {
|
||||
const prompt: McpPrompt = {
|
||||
name: "test",
|
||||
description: "Test prompt",
|
||||
arguments: [{ name: "arg1", description: "Arg 1", required: false }],
|
||||
}
|
||||
|
||||
const result = parsePromptArguments(prompt, "")
|
||||
expect(result).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -146,8 +146,13 @@ export function parsePromptArguments(prompt: McpPrompt, argsString: string): Rec
|
|||
return {}
|
||||
}
|
||||
|
||||
const trimmedArgs = argsString.trim()
|
||||
if (trimmedArgs.length === 0) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const args: Record<string, unknown> = {}
|
||||
const parts = argsString.trim().split(/\s+/)
|
||||
const parts = trimmedArgs.split(/\s+/)
|
||||
|
||||
// Simple positional argument parsing
|
||||
// In a more sophisticated implementation, we could support named arguments
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { ModelRecord, RouterModels } from "./api"
|
|||
// Command interface for frontend/backend communication
|
||||
export interface Command {
|
||||
name: string
|
||||
source: "global" | "project" | "built-in"
|
||||
source: "global" | "project" | "built-in" | "mcp"
|
||||
filePath?: string
|
||||
description?: string
|
||||
argumentHint?: string
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue