llvm-project/lldb/unittests/Protocol/ProtocolMCPTest.cpp
John Harrison 350f6abb83
[lldb] Adjusting the base MCP protocol types per the spec. (#153297)
* This adjusts the `Request`/`Response` types to have an `id` that is
either a string or a number.
* Merges 'Error' into 'Response' to have a single response type that
represents both errors and results.
* Adjusts the `Error.data` field to by any JSON value.
* Adds `operator==` support to the base protocol types and simplifies
the tests.
2025-08-12 17:56:52 -07:00

307 lines
10 KiB
C++

//===-- ProtocolMCPTest.cpp -----------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "TestingSupport/TestUtilities.h"
#include "lldb/Protocol/MCP/Protocol.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
using namespace lldb;
using namespace lldb_private;
using namespace lldb_protocol::mcp;
TEST(ProtocolMCPTest, Request) {
Request request;
request.id = 1;
request.method = "foo";
request.params = llvm::json::Object{{"key", "value"}};
llvm::Expected<Request> deserialized_request = roundtripJSON(request);
ASSERT_THAT_EXPECTED(deserialized_request, llvm::Succeeded());
EXPECT_EQ(request.id, deserialized_request->id);
EXPECT_EQ(request.method, deserialized_request->method);
EXPECT_EQ(request.params, deserialized_request->params);
}
TEST(ProtocolMCPTest, Response) {
Response response;
response.id = 1;
response.result = llvm::json::Object{{"key", "value"}};
llvm::Expected<Response> deserialized_response = roundtripJSON(response);
ASSERT_THAT_EXPECTED(deserialized_response, llvm::Succeeded());
EXPECT_EQ(response.id, deserialized_response->id);
EXPECT_EQ(response.result, deserialized_response->result);
}
TEST(ProtocolMCPTest, Notification) {
Notification notification;
notification.method = "notifyMethod";
notification.params = llvm::json::Object{{"key", "value"}};
llvm::Expected<Notification> deserialized_notification =
roundtripJSON(notification);
ASSERT_THAT_EXPECTED(deserialized_notification, llvm::Succeeded());
EXPECT_EQ(notification.method, deserialized_notification->method);
EXPECT_EQ(notification.params, deserialized_notification->params);
}
TEST(ProtocolMCPTest, ToolCapability) {
ToolCapability tool_capability;
tool_capability.listChanged = true;
llvm::Expected<ToolCapability> deserialized_tool_capability =
roundtripJSON(tool_capability);
ASSERT_THAT_EXPECTED(deserialized_tool_capability, llvm::Succeeded());
EXPECT_EQ(tool_capability.listChanged,
deserialized_tool_capability->listChanged);
}
TEST(ProtocolMCPTest, Capabilities) {
ToolCapability tool_capability;
tool_capability.listChanged = true;
Capabilities capabilities;
capabilities.tools = tool_capability;
llvm::Expected<Capabilities> deserialized_capabilities =
roundtripJSON(capabilities);
ASSERT_THAT_EXPECTED(deserialized_capabilities, llvm::Succeeded());
EXPECT_EQ(capabilities.tools.listChanged,
deserialized_capabilities->tools.listChanged);
}
TEST(ProtocolMCPTest, TextContent) {
TextContent text_content;
text_content.text = "Sample text";
llvm::Expected<TextContent> deserialized_text_content =
roundtripJSON(text_content);
ASSERT_THAT_EXPECTED(deserialized_text_content, llvm::Succeeded());
EXPECT_EQ(text_content.text, deserialized_text_content->text);
}
TEST(ProtocolMCPTest, TextResult) {
TextContent text_content1;
text_content1.text = "Text 1";
TextContent text_content2;
text_content2.text = "Text 2";
TextResult text_result;
text_result.content = {text_content1, text_content2};
text_result.isError = true;
llvm::Expected<TextResult> deserialized_text_result =
roundtripJSON(text_result);
ASSERT_THAT_EXPECTED(deserialized_text_result, llvm::Succeeded());
EXPECT_EQ(text_result.isError, deserialized_text_result->isError);
ASSERT_EQ(text_result.content.size(),
deserialized_text_result->content.size());
EXPECT_EQ(text_result.content[0].text,
deserialized_text_result->content[0].text);
EXPECT_EQ(text_result.content[1].text,
deserialized_text_result->content[1].text);
}
TEST(ProtocolMCPTest, ToolDefinition) {
ToolDefinition tool_definition;
tool_definition.name = "ToolName";
tool_definition.description = "Tool Description";
tool_definition.inputSchema =
llvm::json::Object{{"schemaKey", "schemaValue"}};
llvm::Expected<ToolDefinition> deserialized_tool_definition =
roundtripJSON(tool_definition);
ASSERT_THAT_EXPECTED(deserialized_tool_definition, llvm::Succeeded());
EXPECT_EQ(tool_definition.name, deserialized_tool_definition->name);
EXPECT_EQ(tool_definition.description,
deserialized_tool_definition->description);
EXPECT_EQ(tool_definition.inputSchema,
deserialized_tool_definition->inputSchema);
}
TEST(ProtocolMCPTest, MessageWithRequest) {
Request request;
request.id = 1;
request.method = "test_method";
request.params = llvm::json::Object{{"param", "value"}};
Message message = request;
llvm::Expected<Message> deserialized_message = roundtripJSON(message);
ASSERT_THAT_EXPECTED(deserialized_message, llvm::Succeeded());
ASSERT_TRUE(std::holds_alternative<Request>(*deserialized_message));
const Request &deserialized_request =
std::get<Request>(*deserialized_message);
EXPECT_EQ(request, deserialized_request);
}
TEST(ProtocolMCPTest, MessageWithResponse) {
Response response;
response.id = 2;
response.result = llvm::json::Object{{"result", "success"}};
Message message = response;
llvm::Expected<Message> deserialized_message = roundtripJSON(message);
ASSERT_THAT_EXPECTED(deserialized_message, llvm::Succeeded());
ASSERT_TRUE(std::holds_alternative<Response>(*deserialized_message));
const Response &deserialized_response =
std::get<Response>(*deserialized_message);
EXPECT_EQ(response, deserialized_response);
}
TEST(ProtocolMCPTest, MessageWithNotification) {
Notification notification;
notification.method = "notification_method";
notification.params = llvm::json::Object{{"notify", "data"}};
Message message = notification;
llvm::Expected<Message> deserialized_message = roundtripJSON(message);
ASSERT_THAT_EXPECTED(deserialized_message, llvm::Succeeded());
ASSERT_TRUE(std::holds_alternative<Notification>(*deserialized_message));
const Notification &deserialized_notification =
std::get<Notification>(*deserialized_message);
EXPECT_EQ(notification, deserialized_notification);
}
TEST(ProtocolMCPTest, MessageWithErrorResponse) {
Error error;
error.code = -32603;
error.message = "Internal error";
Response error_response;
error_response.id = 3;
error_response.result = error;
Message message = error_response;
llvm::Expected<Message> deserialized_message = roundtripJSON(message);
ASSERT_THAT_EXPECTED(deserialized_message, llvm::Succeeded());
ASSERT_TRUE(std::holds_alternative<Response>(*deserialized_message));
const Response &deserialized_error =
std::get<Response>(*deserialized_message);
EXPECT_EQ(error_response, deserialized_error);
}
TEST(ProtocolMCPTest, Resource) {
Resource resource;
resource.uri = "resource://example/test";
resource.name = "Test Resource";
resource.description = "A test resource for unit testing";
resource.mimeType = "text/plain";
llvm::Expected<Resource> deserialized_resource = roundtripJSON(resource);
ASSERT_THAT_EXPECTED(deserialized_resource, llvm::Succeeded());
EXPECT_EQ(resource.uri, deserialized_resource->uri);
EXPECT_EQ(resource.name, deserialized_resource->name);
EXPECT_EQ(resource.description, deserialized_resource->description);
EXPECT_EQ(resource.mimeType, deserialized_resource->mimeType);
}
TEST(ProtocolMCPTest, ResourceWithoutOptionals) {
Resource resource;
resource.uri = "resource://example/minimal";
resource.name = "Minimal Resource";
llvm::Expected<Resource> deserialized_resource = roundtripJSON(resource);
ASSERT_THAT_EXPECTED(deserialized_resource, llvm::Succeeded());
EXPECT_EQ(resource.uri, deserialized_resource->uri);
EXPECT_EQ(resource.name, deserialized_resource->name);
EXPECT_TRUE(deserialized_resource->description.empty());
EXPECT_TRUE(deserialized_resource->mimeType.empty());
}
TEST(ProtocolMCPTest, ResourceContents) {
ResourceContents contents;
contents.uri = "resource://example/content";
contents.text = "This is the content of the resource";
contents.mimeType = "text/plain";
llvm::Expected<ResourceContents> deserialized_contents =
roundtripJSON(contents);
ASSERT_THAT_EXPECTED(deserialized_contents, llvm::Succeeded());
EXPECT_EQ(contents.uri, deserialized_contents->uri);
EXPECT_EQ(contents.text, deserialized_contents->text);
EXPECT_EQ(contents.mimeType, deserialized_contents->mimeType);
}
TEST(ProtocolMCPTest, ResourceContentsWithoutMimeType) {
ResourceContents contents;
contents.uri = "resource://example/content-no-mime";
contents.text = "Content without mime type specified";
llvm::Expected<ResourceContents> deserialized_contents =
roundtripJSON(contents);
ASSERT_THAT_EXPECTED(deserialized_contents, llvm::Succeeded());
EXPECT_EQ(contents.uri, deserialized_contents->uri);
EXPECT_EQ(contents.text, deserialized_contents->text);
EXPECT_TRUE(deserialized_contents->mimeType.empty());
}
TEST(ProtocolMCPTest, ResourceResult) {
ResourceContents contents1;
contents1.uri = "resource://example/content1";
contents1.text = "First resource content";
contents1.mimeType = "text/plain";
ResourceContents contents2;
contents2.uri = "resource://example/content2";
contents2.text = "Second resource content";
contents2.mimeType = "application/json";
ResourceResult result;
result.contents = {contents1, contents2};
llvm::Expected<ResourceResult> deserialized_result = roundtripJSON(result);
ASSERT_THAT_EXPECTED(deserialized_result, llvm::Succeeded());
ASSERT_EQ(result.contents.size(), deserialized_result->contents.size());
EXPECT_EQ(result.contents[0].uri, deserialized_result->contents[0].uri);
EXPECT_EQ(result.contents[0].text, deserialized_result->contents[0].text);
EXPECT_EQ(result.contents[0].mimeType,
deserialized_result->contents[0].mimeType);
EXPECT_EQ(result.contents[1].uri, deserialized_result->contents[1].uri);
EXPECT_EQ(result.contents[1].text, deserialized_result->contents[1].text);
EXPECT_EQ(result.contents[1].mimeType,
deserialized_result->contents[1].mimeType);
}
TEST(ProtocolMCPTest, ResourceResultEmpty) {
ResourceResult result;
llvm::Expected<ResourceResult> deserialized_result = roundtripJSON(result);
ASSERT_THAT_EXPECTED(deserialized_result, llvm::Succeeded());
EXPECT_TRUE(deserialized_result->contents.empty());
}