| 1 | //===-- ProtocolMCPTest.cpp -----------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "Plugins/Protocol/MCP/Protocol.h" |
| 10 | #include "TestingSupport/TestUtilities.h" |
| 11 | #include "llvm/Testing/Support/Error.h" |
| 12 | #include "gtest/gtest.h" |
| 13 | |
| 14 | using namespace lldb; |
| 15 | using namespace lldb_private; |
| 16 | using namespace lldb_private::mcp::protocol; |
| 17 | |
| 18 | TEST(ProtocolMCPTest, Request) { |
| 19 | Request request; |
| 20 | request.id = 1; |
| 21 | request.method = "foo" ; |
| 22 | request.params = llvm::json::Object{{.K: "key" , .V: "value" }}; |
| 23 | |
| 24 | llvm::Expected<Request> deserialized_request = roundtripJSON(input: request); |
| 25 | ASSERT_THAT_EXPECTED(deserialized_request, llvm::Succeeded()); |
| 26 | |
| 27 | EXPECT_EQ(request.id, deserialized_request->id); |
| 28 | EXPECT_EQ(request.method, deserialized_request->method); |
| 29 | EXPECT_EQ(request.params, deserialized_request->params); |
| 30 | } |
| 31 | |
| 32 | TEST(ProtocolMCPTest, Response) { |
| 33 | Response response; |
| 34 | response.id = 1; |
| 35 | response.result = llvm::json::Object{{.K: "key" , .V: "value" }}; |
| 36 | |
| 37 | llvm::Expected<Response> deserialized_response = roundtripJSON(input: response); |
| 38 | ASSERT_THAT_EXPECTED(deserialized_response, llvm::Succeeded()); |
| 39 | |
| 40 | EXPECT_EQ(response.id, deserialized_response->id); |
| 41 | EXPECT_EQ(response.result, deserialized_response->result); |
| 42 | } |
| 43 | |
| 44 | TEST(ProtocolMCPTest, Notification) { |
| 45 | Notification notification; |
| 46 | notification.method = "notifyMethod" ; |
| 47 | notification.params = llvm::json::Object{{.K: "key" , .V: "value" }}; |
| 48 | |
| 49 | llvm::Expected<Notification> deserialized_notification = |
| 50 | roundtripJSON(input: notification); |
| 51 | ASSERT_THAT_EXPECTED(deserialized_notification, llvm::Succeeded()); |
| 52 | |
| 53 | EXPECT_EQ(notification.method, deserialized_notification->method); |
| 54 | EXPECT_EQ(notification.params, deserialized_notification->params); |
| 55 | } |
| 56 | |
| 57 | TEST(ProtocolMCPTest, ToolCapability) { |
| 58 | ToolCapability tool_capability; |
| 59 | tool_capability.listChanged = true; |
| 60 | |
| 61 | llvm::Expected<ToolCapability> deserialized_tool_capability = |
| 62 | roundtripJSON(input: tool_capability); |
| 63 | ASSERT_THAT_EXPECTED(deserialized_tool_capability, llvm::Succeeded()); |
| 64 | |
| 65 | EXPECT_EQ(tool_capability.listChanged, |
| 66 | deserialized_tool_capability->listChanged); |
| 67 | } |
| 68 | |
| 69 | TEST(ProtocolMCPTest, Capabilities) { |
| 70 | ToolCapability tool_capability; |
| 71 | tool_capability.listChanged = true; |
| 72 | |
| 73 | Capabilities capabilities; |
| 74 | capabilities.tools = tool_capability; |
| 75 | |
| 76 | llvm::Expected<Capabilities> deserialized_capabilities = |
| 77 | roundtripJSON(input: capabilities); |
| 78 | ASSERT_THAT_EXPECTED(deserialized_capabilities, llvm::Succeeded()); |
| 79 | |
| 80 | EXPECT_EQ(capabilities.tools.listChanged, |
| 81 | deserialized_capabilities->tools.listChanged); |
| 82 | } |
| 83 | |
| 84 | TEST(ProtocolMCPTest, TextContent) { |
| 85 | TextContent text_content; |
| 86 | text_content.text = "Sample text" ; |
| 87 | |
| 88 | llvm::Expected<TextContent> deserialized_text_content = |
| 89 | roundtripJSON(input: text_content); |
| 90 | ASSERT_THAT_EXPECTED(deserialized_text_content, llvm::Succeeded()); |
| 91 | |
| 92 | EXPECT_EQ(text_content.text, deserialized_text_content->text); |
| 93 | } |
| 94 | |
| 95 | TEST(ProtocolMCPTest, TextResult) { |
| 96 | TextContent text_content1; |
| 97 | text_content1.text = "Text 1" ; |
| 98 | |
| 99 | TextContent text_content2; |
| 100 | text_content2.text = "Text 2" ; |
| 101 | |
| 102 | TextResult text_result; |
| 103 | text_result.content = {text_content1, text_content2}; |
| 104 | text_result.isError = true; |
| 105 | |
| 106 | llvm::Expected<TextResult> deserialized_text_result = |
| 107 | roundtripJSON(input: text_result); |
| 108 | ASSERT_THAT_EXPECTED(deserialized_text_result, llvm::Succeeded()); |
| 109 | |
| 110 | EXPECT_EQ(text_result.isError, deserialized_text_result->isError); |
| 111 | ASSERT_EQ(text_result.content.size(), |
| 112 | deserialized_text_result->content.size()); |
| 113 | EXPECT_EQ(text_result.content[0].text, |
| 114 | deserialized_text_result->content[0].text); |
| 115 | EXPECT_EQ(text_result.content[1].text, |
| 116 | deserialized_text_result->content[1].text); |
| 117 | } |
| 118 | |
| 119 | TEST(ProtocolMCPTest, ToolDefinition) { |
| 120 | ToolDefinition tool_definition; |
| 121 | tool_definition.name = "ToolName" ; |
| 122 | tool_definition.description = "Tool Description" ; |
| 123 | tool_definition.inputSchema = |
| 124 | llvm::json::Object{{.K: "schemaKey" , .V: "schemaValue" }}; |
| 125 | |
| 126 | llvm::Expected<ToolDefinition> deserialized_tool_definition = |
| 127 | roundtripJSON(input: tool_definition); |
| 128 | ASSERT_THAT_EXPECTED(deserialized_tool_definition, llvm::Succeeded()); |
| 129 | |
| 130 | EXPECT_EQ(tool_definition.name, deserialized_tool_definition->name); |
| 131 | EXPECT_EQ(tool_definition.description, |
| 132 | deserialized_tool_definition->description); |
| 133 | EXPECT_EQ(tool_definition.inputSchema, |
| 134 | deserialized_tool_definition->inputSchema); |
| 135 | } |
| 136 | |
| 137 | TEST(ProtocolMCPTest, MessageWithRequest) { |
| 138 | Request request; |
| 139 | request.id = 1; |
| 140 | request.method = "test_method" ; |
| 141 | request.params = llvm::json::Object{{.K: "param" , .V: "value" }}; |
| 142 | |
| 143 | Message message = request; |
| 144 | |
| 145 | llvm::Expected<Message> deserialized_message = roundtripJSON(input: message); |
| 146 | ASSERT_THAT_EXPECTED(deserialized_message, llvm::Succeeded()); |
| 147 | |
| 148 | ASSERT_TRUE(std::holds_alternative<Request>(*deserialized_message)); |
| 149 | const Request &deserialized_request = |
| 150 | std::get<Request>(v&: *deserialized_message); |
| 151 | |
| 152 | EXPECT_EQ(request.id, deserialized_request.id); |
| 153 | EXPECT_EQ(request.method, deserialized_request.method); |
| 154 | EXPECT_EQ(request.params, deserialized_request.params); |
| 155 | } |
| 156 | |
| 157 | TEST(ProtocolMCPTest, MessageWithResponse) { |
| 158 | Response response; |
| 159 | response.id = 2; |
| 160 | response.result = llvm::json::Object{{.K: "result" , .V: "success" }}; |
| 161 | |
| 162 | Message message = response; |
| 163 | |
| 164 | llvm::Expected<Message> deserialized_message = roundtripJSON(input: message); |
| 165 | ASSERT_THAT_EXPECTED(deserialized_message, llvm::Succeeded()); |
| 166 | |
| 167 | ASSERT_TRUE(std::holds_alternative<Response>(*deserialized_message)); |
| 168 | const Response &deserialized_response = |
| 169 | std::get<Response>(v&: *deserialized_message); |
| 170 | |
| 171 | EXPECT_EQ(response.id, deserialized_response.id); |
| 172 | EXPECT_EQ(response.result, deserialized_response.result); |
| 173 | } |
| 174 | |
| 175 | TEST(ProtocolMCPTest, MessageWithNotification) { |
| 176 | Notification notification; |
| 177 | notification.method = "notification_method" ; |
| 178 | notification.params = llvm::json::Object{{.K: "notify" , .V: "data" }}; |
| 179 | |
| 180 | Message message = notification; |
| 181 | |
| 182 | llvm::Expected<Message> deserialized_message = roundtripJSON(input: message); |
| 183 | ASSERT_THAT_EXPECTED(deserialized_message, llvm::Succeeded()); |
| 184 | |
| 185 | ASSERT_TRUE(std::holds_alternative<Notification>(*deserialized_message)); |
| 186 | const Notification &deserialized_notification = |
| 187 | std::get<Notification>(v&: *deserialized_message); |
| 188 | |
| 189 | EXPECT_EQ(notification.method, deserialized_notification.method); |
| 190 | EXPECT_EQ(notification.params, deserialized_notification.params); |
| 191 | } |
| 192 | |
| 193 | TEST(ProtocolMCPTest, MessageWithError) { |
| 194 | ErrorInfo error_info; |
| 195 | error_info.code = -32603; |
| 196 | error_info.message = "Internal error" ; |
| 197 | |
| 198 | Error error; |
| 199 | error.id = 3; |
| 200 | error.error = error_info; |
| 201 | |
| 202 | Message message = error; |
| 203 | |
| 204 | llvm::Expected<Message> deserialized_message = roundtripJSON(input: message); |
| 205 | ASSERT_THAT_EXPECTED(deserialized_message, llvm::Succeeded()); |
| 206 | |
| 207 | ASSERT_TRUE(std::holds_alternative<Error>(*deserialized_message)); |
| 208 | const Error &deserialized_error = std::get<Error>(v&: *deserialized_message); |
| 209 | |
| 210 | EXPECT_EQ(error.id, deserialized_error.id); |
| 211 | EXPECT_EQ(error.error.code, deserialized_error.error.code); |
| 212 | EXPECT_EQ(error.error.message, deserialized_error.error.message); |
| 213 | } |
| 214 | |
| 215 | TEST(ProtocolMCPTest, ResponseWithError) { |
| 216 | ErrorInfo error_info; |
| 217 | error_info.code = -32700; |
| 218 | error_info.message = "Parse error" ; |
| 219 | |
| 220 | Response response; |
| 221 | response.id = 4; |
| 222 | response.error = error_info; |
| 223 | |
| 224 | llvm::Expected<Response> deserialized_response = roundtripJSON(input: response); |
| 225 | ASSERT_THAT_EXPECTED(deserialized_response, llvm::Succeeded()); |
| 226 | |
| 227 | EXPECT_EQ(response.id, deserialized_response->id); |
| 228 | EXPECT_FALSE(deserialized_response->result.has_value()); |
| 229 | ASSERT_TRUE(deserialized_response->error.has_value()); |
| 230 | EXPECT_EQ(response.error->code, deserialized_response->error->code); |
| 231 | EXPECT_EQ(response.error->message, deserialized_response->error->message); |
| 232 | } |
| 233 | |
| 234 | TEST(ProtocolMCPTest, Resource) { |
| 235 | Resource resource; |
| 236 | resource.uri = "resource://example/test" ; |
| 237 | resource.name = "Test Resource" ; |
| 238 | resource.description = "A test resource for unit testing" ; |
| 239 | resource.mimeType = "text/plain" ; |
| 240 | |
| 241 | llvm::Expected<Resource> deserialized_resource = roundtripJSON(input: resource); |
| 242 | ASSERT_THAT_EXPECTED(deserialized_resource, llvm::Succeeded()); |
| 243 | |
| 244 | EXPECT_EQ(resource.uri, deserialized_resource->uri); |
| 245 | EXPECT_EQ(resource.name, deserialized_resource->name); |
| 246 | EXPECT_EQ(resource.description, deserialized_resource->description); |
| 247 | EXPECT_EQ(resource.mimeType, deserialized_resource->mimeType); |
| 248 | } |
| 249 | |
| 250 | TEST(ProtocolMCPTest, ResourceWithoutOptionals) { |
| 251 | Resource resource; |
| 252 | resource.uri = "resource://example/minimal" ; |
| 253 | resource.name = "Minimal Resource" ; |
| 254 | |
| 255 | llvm::Expected<Resource> deserialized_resource = roundtripJSON(input: resource); |
| 256 | ASSERT_THAT_EXPECTED(deserialized_resource, llvm::Succeeded()); |
| 257 | |
| 258 | EXPECT_EQ(resource.uri, deserialized_resource->uri); |
| 259 | EXPECT_EQ(resource.name, deserialized_resource->name); |
| 260 | EXPECT_TRUE(deserialized_resource->description.empty()); |
| 261 | EXPECT_TRUE(deserialized_resource->mimeType.empty()); |
| 262 | } |
| 263 | |
| 264 | TEST(ProtocolMCPTest, ResourceContents) { |
| 265 | ResourceContents contents; |
| 266 | contents.uri = "resource://example/content" ; |
| 267 | contents.text = "This is the content of the resource" ; |
| 268 | contents.mimeType = "text/plain" ; |
| 269 | |
| 270 | llvm::Expected<ResourceContents> deserialized_contents = |
| 271 | roundtripJSON(input: contents); |
| 272 | ASSERT_THAT_EXPECTED(deserialized_contents, llvm::Succeeded()); |
| 273 | |
| 274 | EXPECT_EQ(contents.uri, deserialized_contents->uri); |
| 275 | EXPECT_EQ(contents.text, deserialized_contents->text); |
| 276 | EXPECT_EQ(contents.mimeType, deserialized_contents->mimeType); |
| 277 | } |
| 278 | |
| 279 | TEST(ProtocolMCPTest, ResourceContentsWithoutMimeType) { |
| 280 | ResourceContents contents; |
| 281 | contents.uri = "resource://example/content-no-mime" ; |
| 282 | contents.text = "Content without mime type specified" ; |
| 283 | |
| 284 | llvm::Expected<ResourceContents> deserialized_contents = |
| 285 | roundtripJSON(input: contents); |
| 286 | ASSERT_THAT_EXPECTED(deserialized_contents, llvm::Succeeded()); |
| 287 | |
| 288 | EXPECT_EQ(contents.uri, deserialized_contents->uri); |
| 289 | EXPECT_EQ(contents.text, deserialized_contents->text); |
| 290 | EXPECT_TRUE(deserialized_contents->mimeType.empty()); |
| 291 | } |
| 292 | |
| 293 | TEST(ProtocolMCPTest, ResourceResult) { |
| 294 | ResourceContents contents1; |
| 295 | contents1.uri = "resource://example/content1" ; |
| 296 | contents1.text = "First resource content" ; |
| 297 | contents1.mimeType = "text/plain" ; |
| 298 | |
| 299 | ResourceContents contents2; |
| 300 | contents2.uri = "resource://example/content2" ; |
| 301 | contents2.text = "Second resource content" ; |
| 302 | contents2.mimeType = "application/json" ; |
| 303 | |
| 304 | ResourceResult result; |
| 305 | result.contents = {contents1, contents2}; |
| 306 | |
| 307 | llvm::Expected<ResourceResult> deserialized_result = roundtripJSON(input: result); |
| 308 | ASSERT_THAT_EXPECTED(deserialized_result, llvm::Succeeded()); |
| 309 | |
| 310 | ASSERT_EQ(result.contents.size(), deserialized_result->contents.size()); |
| 311 | |
| 312 | EXPECT_EQ(result.contents[0].uri, deserialized_result->contents[0].uri); |
| 313 | EXPECT_EQ(result.contents[0].text, deserialized_result->contents[0].text); |
| 314 | EXPECT_EQ(result.contents[0].mimeType, |
| 315 | deserialized_result->contents[0].mimeType); |
| 316 | |
| 317 | EXPECT_EQ(result.contents[1].uri, deserialized_result->contents[1].uri); |
| 318 | EXPECT_EQ(result.contents[1].text, deserialized_result->contents[1].text); |
| 319 | EXPECT_EQ(result.contents[1].mimeType, |
| 320 | deserialized_result->contents[1].mimeType); |
| 321 | } |
| 322 | |
| 323 | TEST(ProtocolMCPTest, ResourceResultEmpty) { |
| 324 | ResourceResult result; |
| 325 | |
| 326 | llvm::Expected<ResourceResult> deserialized_result = roundtripJSON(input: result); |
| 327 | ASSERT_THAT_EXPECTED(deserialized_result, llvm::Succeeded()); |
| 328 | |
| 329 | EXPECT_TRUE(deserialized_result->contents.empty()); |
| 330 | } |
| 331 | |