299 lines
9.8 KiB
C++
299 lines
9.8 KiB
C++
/**
|
|
* @file MCPTypesTests.cpp
|
|
* @brief Integration tests for MCP Types (15 TI)
|
|
*/
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include "shared/mcp/MCPTypes.hpp"
|
|
|
|
using namespace aissia::mcp;
|
|
using json = nlohmann::json;
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_001: MCPTool toJson
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_001_MCPToolToJson", "[mcp][types]") {
|
|
MCPTool tool;
|
|
tool.name = "read_file";
|
|
tool.description = "Read a file from the filesystem";
|
|
tool.inputSchema = {
|
|
{"type", "object"},
|
|
{"properties", {
|
|
{"path", {{"type", "string"}}}
|
|
}},
|
|
{"required", json::array({"path"})}
|
|
};
|
|
|
|
json j = tool.toJson();
|
|
|
|
REQUIRE(j["name"] == "read_file");
|
|
REQUIRE(j["description"] == "Read a file from the filesystem");
|
|
REQUIRE(j["inputSchema"]["type"] == "object");
|
|
REQUIRE(j["inputSchema"]["properties"]["path"]["type"] == "string");
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_002: MCPTool fromJson
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_002_MCPToolFromJson", "[mcp][types]") {
|
|
json j = {
|
|
{"name", "write_file"},
|
|
{"description", "Write content to a file"},
|
|
{"inputSchema", {
|
|
{"type", "object"},
|
|
{"properties", {
|
|
{"path", {{"type", "string"}}},
|
|
{"content", {{"type", "string"}}}
|
|
}}
|
|
}}
|
|
};
|
|
|
|
auto tool = MCPTool::fromJson(j);
|
|
|
|
REQUIRE(tool.name == "write_file");
|
|
REQUIRE(tool.description == "Write content to a file");
|
|
REQUIRE(tool.inputSchema["type"] == "object");
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_003: MCPTool fromJson with missing fields
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_003_MCPToolFromJsonMissingFields", "[mcp][types]") {
|
|
json j = {{"name", "minimal_tool"}};
|
|
|
|
auto tool = MCPTool::fromJson(j);
|
|
|
|
REQUIRE(tool.name == "minimal_tool");
|
|
REQUIRE(tool.description == "");
|
|
REQUIRE(tool.inputSchema.is_object());
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_004: MCPResource fromJson
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_004_MCPResourceFromJson", "[mcp][types]") {
|
|
json j = {
|
|
{"uri", "file:///home/user/doc.txt"},
|
|
{"name", "Document"},
|
|
{"description", "A text document"},
|
|
{"mimeType", "text/plain"}
|
|
};
|
|
|
|
auto resource = MCPResource::fromJson(j);
|
|
|
|
REQUIRE(resource.uri == "file:///home/user/doc.txt");
|
|
REQUIRE(resource.name == "Document");
|
|
REQUIRE(resource.description == "A text document");
|
|
REQUIRE(resource.mimeType == "text/plain");
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_005: MCPToolResult toJson
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_005_MCPToolResultToJson", "[mcp][types]") {
|
|
MCPToolResult result;
|
|
result.content = {
|
|
{{"type", "text"}, {"text", "File contents here"}},
|
|
{{"type", "text"}, {"text", "More content"}}
|
|
};
|
|
result.isError = false;
|
|
|
|
json j = result.toJson();
|
|
|
|
REQUIRE(j["content"].size() == 2);
|
|
REQUIRE(j["content"][0]["type"] == "text");
|
|
REQUIRE(j["isError"] == false);
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_006: MCPCapabilities fromJson
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_006_MCPCapabilitiesFromJson", "[mcp][types]") {
|
|
json j = {
|
|
{"tools", json::object()},
|
|
{"resources", {{"subscribe", true}}},
|
|
{"prompts", json::object()}
|
|
};
|
|
|
|
auto caps = MCPCapabilities::fromJson(j);
|
|
|
|
REQUIRE(caps.hasTools == true);
|
|
REQUIRE(caps.hasResources == true);
|
|
REQUIRE(caps.hasPrompts == true);
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_007: MCPCapabilities empty
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_007_MCPCapabilitiesEmpty", "[mcp][types]") {
|
|
json j = json::object();
|
|
|
|
auto caps = MCPCapabilities::fromJson(j);
|
|
|
|
REQUIRE(caps.hasTools == false);
|
|
REQUIRE(caps.hasResources == false);
|
|
REQUIRE(caps.hasPrompts == false);
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_008: MCPServerInfo fromJson
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_008_MCPServerInfoFromJson", "[mcp][types]") {
|
|
json j = {
|
|
{"name", "filesystem-server"},
|
|
{"version", "1.2.3"},
|
|
{"capabilities", {
|
|
{"tools", json::object()}
|
|
}}
|
|
};
|
|
|
|
auto info = MCPServerInfo::fromJson(j);
|
|
|
|
REQUIRE(info.name == "filesystem-server");
|
|
REQUIRE(info.version == "1.2.3");
|
|
REQUIRE(info.capabilities.hasTools == true);
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_009: JsonRpcRequest toJson
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_009_JsonRpcRequestToJson", "[mcp][types]") {
|
|
JsonRpcRequest request;
|
|
request.id = 42;
|
|
request.method = "tools/call";
|
|
request.params = {{"name", "read_file"}, {"arguments", {{"path", "/tmp/test"}}}};
|
|
|
|
json j = request.toJson();
|
|
|
|
REQUIRE(j["jsonrpc"] == "2.0");
|
|
REQUIRE(j["id"] == 42);
|
|
REQUIRE(j["method"] == "tools/call");
|
|
REQUIRE(j["params"]["name"] == "read_file");
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_010: JsonRpcResponse fromJson
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_010_JsonRpcResponseFromJson", "[mcp][types]") {
|
|
json j = {
|
|
{"jsonrpc", "2.0"},
|
|
{"id", 42},
|
|
{"result", {{"tools", json::array()}}}
|
|
};
|
|
|
|
auto response = JsonRpcResponse::fromJson(j);
|
|
|
|
REQUIRE(response.jsonrpc == "2.0");
|
|
REQUIRE(response.id == 42);
|
|
REQUIRE(response.result.has_value());
|
|
REQUIRE(response.result.value()["tools"].is_array());
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_011: JsonRpcResponse isError
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_011_JsonRpcResponseIsError", "[mcp][types]") {
|
|
json errorJson = {
|
|
{"jsonrpc", "2.0"},
|
|
{"id", 1},
|
|
{"error", {{"code", -32600}, {"message", "Invalid Request"}}}
|
|
};
|
|
|
|
auto response = JsonRpcResponse::fromJson(errorJson);
|
|
|
|
REQUIRE(response.isError() == true);
|
|
REQUIRE(response.error.has_value());
|
|
REQUIRE(response.error.value()["code"] == -32600);
|
|
REQUIRE(response.error.value()["message"] == "Invalid Request");
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_012: MCPServerConfig fromJson
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_012_MCPServerConfigFromJson", "[mcp][types]") {
|
|
json j = {
|
|
{"command", "mcp-server-filesystem"},
|
|
{"args", json::array({"--root", "/home"})},
|
|
{"env", {{"DEBUG", "true"}}},
|
|
{"enabled", true}
|
|
};
|
|
|
|
auto config = MCPServerConfig::fromJson("filesystem", j);
|
|
|
|
REQUIRE(config.name == "filesystem");
|
|
REQUIRE(config.command == "mcp-server-filesystem");
|
|
REQUIRE(config.args.size() == 2);
|
|
REQUIRE(config.args[0] == "--root");
|
|
REQUIRE(config.args[1] == "/home");
|
|
REQUIRE(config.env["DEBUG"] == "true");
|
|
REQUIRE(config.enabled == true);
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_013: MCPServerConfig env expansion
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_013_MCPServerConfigEnvExpansion", "[mcp][types]") {
|
|
json j = {
|
|
{"command", "mcp-server"},
|
|
{"env", {{"API_KEY", "${MY_API_KEY}"}}}
|
|
};
|
|
|
|
auto config = MCPServerConfig::fromJson("test", j);
|
|
|
|
// Note: Actual env expansion happens in MCPClient, not in fromJson
|
|
// This test verifies the raw value is stored
|
|
REQUIRE(config.env["API_KEY"] == "${MY_API_KEY}");
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_014: MCPServerConfig disabled
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_014_MCPServerConfigDisabled", "[mcp][types]") {
|
|
json j = {
|
|
{"command", "some-server"},
|
|
{"enabled", false}
|
|
};
|
|
|
|
auto config = MCPServerConfig::fromJson("disabled_server", j);
|
|
|
|
REQUIRE(config.enabled == false);
|
|
}
|
|
|
|
// ============================================================================
|
|
// TI_TYPES_015: JsonRpcRequest ID increment
|
|
// ============================================================================
|
|
|
|
TEST_CASE("TI_TYPES_015_JsonRpcRequestIdIncrement", "[mcp][types]") {
|
|
JsonRpcRequest req1;
|
|
req1.id = 1;
|
|
req1.method = "test";
|
|
|
|
JsonRpcRequest req2;
|
|
req2.id = 2;
|
|
req2.method = "test";
|
|
|
|
// IDs should be different
|
|
REQUIRE(req1.id != req2.id);
|
|
|
|
// Both should serialize correctly
|
|
json j1 = req1.toJson();
|
|
json j2 = req2.toJson();
|
|
|
|
REQUIRE(j1["id"] == 1);
|
|
REQUIRE(j2["id"] == 2);
|
|
}
|