yams/tests/unit/mcp/mcp_request_routing_catch2_test.cpp

114 lines
4 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <memory>
#include <optional>
#include <string>
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <yams/mcp/mcp_server.h>
#include "../../common/test_helpers_catch2.h"
#include <cstdlib>
using json = nlohmann::json;
using yams::ErrorCode;
using yams::mcp::ITransport;
using yams::mcp::MCPServer;
using yams::mcp::MessageResult;
using yams::mcp::TransportState;
namespace {
class NullTransport : public ITransport {
public:
void send(const json&) override {}
MessageResult receive() override {
return yams::Error{yams::ErrorCode::NotImplemented, "Null transport"};
}
bool isConnected() const override { return false; }
void close() override {}
TransportState getState() const override { return TransportState::Disconnected; }
};
using yams::test::ScopedEnvVar;
using yams::test::SpdlogLevelGuard;
} // namespace
TEST_CASE("MCP routing notifications return notification sentinel errors",
"[mcp][routing][notifications][catch2]") {
auto server = std::make_shared<MCPServer>(std::make_unique<NullTransport>());
SECTION("notifications/initialized") {
json req = {{"jsonrpc", "2.0"},
{"method", "notifications/initialized"},
{"params", json::object()}};
auto mr = server->handleRequestPublic(req);
REQUIRE_FALSE(mr.has_value());
CHECK(mr.error().code == ErrorCode::Success);
CHECK(mr.error().message == "notification");
}
SECTION("notifications/cancelled accepts requestId") {
json req = {{"jsonrpc", "2.0"},
{"method", "notifications/cancelled"},
{"params", {{"requestId", 123}}}};
auto mr = server->handleRequestPublic(req);
REQUIRE_FALSE(mr.has_value());
CHECK(mr.error().code == ErrorCode::Success);
CHECK(mr.error().message == "notification");
}
SECTION("notifications/cancelled rejects missing id") {
json req = {
{"jsonrpc", "2.0"}, {"method", "notifications/cancelled"}, {"params", json::object()}};
auto mr = server->handleRequestPublic(req);
REQUIRE_FALSE(mr.has_value());
CHECK(mr.error().code == ErrorCode::InvalidArgument);
}
}
TEST_CASE("MCP routing logging/setLevel honors extension gating",
"[mcp][routing][logging][extensions][catch2]") {
SpdlogLevelGuard levelGuard;
SECTION("enabled by default returns success result") {
ScopedEnvVar clearExt("YAMS_DISABLE_EXTENSIONS", std::nullopt);
auto server = std::make_shared<MCPServer>(std::make_unique<NullTransport>());
json req = {{"jsonrpc", "2.0"},
{"id", 7},
{"method", "logging/setLevel"},
{"params", {{"level", "debug"}}}};
auto mr = server->handleRequestPublic(req);
REQUIRE(mr.has_value());
auto out = mr.value();
REQUIRE(out.is_object());
CHECK(out["jsonrpc"] == "2.0");
CHECK(out["id"] == 7);
REQUIRE(out.contains("result"));
CHECK(out["result"].is_object());
}
SECTION("disabled returns method not available error response") {
ScopedEnvVar disableExt("YAMS_DISABLE_EXTENSIONS", std::string("1"));
auto server = std::make_shared<MCPServer>(std::make_unique<NullTransport>());
json req = {{"jsonrpc", "2.0"},
{"id", 8},
{"method", "logging/setLevel"},
{"params", {{"level", "warn"}}}};
auto mr = server->handleRequestPublic(req);
REQUIRE(mr.has_value());
auto out = mr.value();
REQUIRE(out.is_object());
CHECK(out["jsonrpc"] == "2.0");
CHECK(out["id"] == 8);
REQUIRE(out.contains("error"));
CHECK(out["error"]["code"] == -32601);
REQUIRE(out["error"]["message"].is_string());
CHECK(out["error"]["message"].get<std::string>().find("extensions disabled") !=
std::string::npos);
}
}