aissia/tests/mcp/MCPTypesTests.cpp

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);
}