/** * @file MCPTypesTests.cpp * @brief Integration tests for MCP Types (15 TI) */ #include #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); }