cursor.integration.test.ts•7.62 kB
/**
* Cursor Editor MCP Client Integration Tests
*
* Tests the Home Assistant MCP integration with Cursor IDE
* Mocks the Cursor LSP client and AI chat features
* Tests:
* - LSP protocol communication
* - In-editor code completion with MCP tools
* - Chat interface with Home Assistant context
* - Configuration persistence
*/
import { describe, it, expect, beforeEach } from "bun:test";
interface LSPNotificationParams {
method: string;
params: Record<string, unknown>;
}
interface LSPRequestParams {
id: number;
method: string;
params: Record<string, unknown>;
}
interface LSPResponse {
jsonrpc: string;
id: number;
result?: unknown;
error?: { code: number; message: string };
}
interface ChatMessage {
role: "user" | "assistant";
content: string;
toolCalls?: Array<{ toolName: string; args: unknown }>;
}
class MockCursorClient {
private messageQueue: LSPNotificationParams[] = [];
private requestQueue: LSPRequestParams[] = [];
private responses: Map<number, LSPResponse> = new Map();
private requestId = 0;
private chatHistory: ChatMessage[] = [];
private mcpConnected = false;
async initialize(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
await Promise.resolve();
this.mcpConnected = true;
this.messageQueue.push({
method: "initialized",
params: { capabilities: { homeAssistant: true } }
});
}
async shutdown(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
await Promise.resolve();
this.mcpConnected = false;
}
async sendRequest(method: string, params: Record<string, unknown>): Promise<LSPResponse> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
await Promise.resolve();
const id = ++this.requestId;
const request: LSPRequestParams = { id, method, params };
this.requestQueue.push(request);
// Simulate response
const response: LSPResponse = {
jsonrpc: "2.0",
id
};
if (method === "mcp/tools") {
response.result = [
{ name: "control", description: "Control Home Assistant" },
{ name: "list_devices", description: "List devices" },
{ name: "history", description: "Get history" }
];
} else if (method === "mcp/execute") {
const executeParams = params;
response.result = {
status: "executed",
tool: executeParams.tool,
result: { message: "Tool executed successfully" }
};
} else {
response.error = { code: -32601, message: "Method not found" };
}
this.responses.set(id, response);
return response;
}
async sendNotification(method: string, params: Record<string, unknown>): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
await Promise.resolve();
this.messageQueue.push({ method, params });
}
async chatWithContext(message: string, _context: unknown): Promise<ChatMessage> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
await Promise.resolve();
const userMessage: ChatMessage = { role: "user", content: message };
this.chatHistory.push(userMessage);
// Simulate AI response with tool suggestion
const assistantMessage: ChatMessage = {
role: "assistant",
content: `I'll help you with that. Let me check the current state of your Home Assistant setup.`,
toolCalls: [
{ toolName: "list_devices", args: {} }
]
};
this.chatHistory.push(assistantMessage);
return assistantMessage;
}
getMessageCount(): number {
return this.messageQueue.length;
}
getRequestCount(): number {
return this.requestQueue.length;
}
getChatHistory(): ChatMessage[] {
return this.chatHistory;
}
isConnected(): boolean {
return this.mcpConnected;
}
getLastMessage(): LSPNotificationParams | undefined {
return this.messageQueue[this.messageQueue.length - 1];
}
getLastRequest(): LSPRequestParams | undefined {
return this.requestQueue[this.requestQueue.length - 1];
}
}
describe("Cursor Editor MCP Client Integration", () => {
let client: MockCursorClient;
beforeEach(() => {
client = new MockCursorClient();
});
it("should initialize the LSP client", async () => {
expect(client.isConnected()).toBe(false);
await client.initialize();
expect(client.isConnected()).toBe(true);
const lastMessage = client.getLastMessage();
expect(lastMessage?.method).toBe("initialized");
});
it("should shutdown gracefully", async () => {
await client.initialize();
expect(client.isConnected()).toBe(true);
await client.shutdown();
expect(client.isConnected()).toBe(false);
});
it("should handle LSP requests", async () => {
await client.initialize();
const response = await client.sendRequest("mcp/tools", {});
expect(response.jsonrpc).toBe("2.0");
expect(response.error).toBeUndefined();
const result = response.result as unknown[];
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
});
it("should execute tools via LSP", async () => {
await client.initialize();
const response = await client.sendRequest("mcp/execute", {
tool: "control",
params: { command: "turn_on", entity_id: "light.bedroom" }
});
expect(response.error).toBeUndefined();
const result = response.result as Record<string, unknown>;
expect(result.status).toBe("executed");
});
it("should handle unknown LSP methods", async () => {
await client.initialize();
const response = await client.sendRequest("unknown/method", {});
expect(response.error).toBeDefined();
expect(response.error?.code).toBe(-32601);
});
it("should send notifications", async () => {
await client.initialize();
await client.sendNotification("mcp/connected", { serverName: "homeassistant" });
expect(client.getMessageCount()).toBeGreaterThan(1);
const lastMessage = client.getLastMessage();
expect(lastMessage?.method).toBe("mcp/connected");
});
it("should support chat with Home Assistant context", async () => {
await client.initialize();
const message = await client.chatWithContext(
"Turn on the bedroom light",
{ context: "home_assistant" }
);
expect(message.role).toBe("assistant");
expect(message.content).toContain("Home Assistant");
expect(message.toolCalls).toBeDefined();
});
it("should maintain chat history", async () => {
await client.initialize();
await client.chatWithContext("What devices do I have?", {});
await client.chatWithContext("Turn on the lights", {});
const history = client.getChatHistory();
expect(history.length).toBeGreaterThan(0);
expect(history.some(m => m.content.includes("devices"))).toBe(true);
});
it("should track LSP requests", async () => {
await client.initialize();
expect(client.getRequestCount()).toBe(0);
await client.sendRequest("mcp/tools", {});
expect(client.getRequestCount()).toBe(1);
await client.sendRequest("mcp/execute", { tool: "control" });
expect(client.getRequestCount()).toBe(2);
});
it("should provide request/response information", async () => {
await client.initialize();
const response = await client.sendRequest("mcp/tools", {});
const lastRequest = client.getLastRequest();
expect(lastRequest?.method).toBe("mcp/tools");
expect(lastRequest?.id).toBeDefined();
if (lastRequest?.id !== undefined) {
expect(response.id).toBe(lastRequest.id);
}
});
});