| // Copyright 2021 The Verible Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "common/lsp/json-rpc-dispatcher.h" |
| |
| #include <algorithm> |
| #include <exception> |
| |
| #include "absl/status/status.h" |
| #include "absl/strings/string_view.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| using nlohmann::json; |
| |
| namespace verible { |
| namespace lsp { |
| TEST(JsonRpcDispatcherTest, Call_GarbledInputRequest) { |
| int write_fun_called = 0; |
| |
| // If the input can't even be parsed, it is reported back to the client |
| JsonRpcDispatcher dispatcher([&](absl::string_view s) { |
| const json j = json::parse(s); |
| EXPECT_TRUE(j.find("error") != j.end()); |
| EXPECT_EQ(j["error"]["code"], JsonRpcDispatcher::kParseError) << s; |
| ++write_fun_called; |
| }); |
| |
| dispatcher.DispatchMessage("This is not even close to Json"); |
| |
| EXPECT_EQ(write_fun_called, 1); // Complain unparseable input. |
| EXPECT_EQ(dispatcher.exception_count(), 1); |
| } |
| |
| TEST(JsonRpcDispatcherTest, Call_MissingMethodInRequest) { |
| // If the request does not contain a method name, it is malformed. |
| int write_fun_called = 0; |
| int notification_fun_called = 0; |
| |
| JsonRpcDispatcher dispatcher([&](absl::string_view s) { |
| const json j = json::parse(s); |
| EXPECT_TRUE(j.find("error") != j.end()); |
| EXPECT_EQ(j["error"]["code"], JsonRpcDispatcher::kMethodNotFound) << s; |
| ++write_fun_called; |
| }); |
| dispatcher.AddNotificationHandler( |
| "foo", [&](const json &j) { ++notification_fun_called; }); |
| |
| dispatcher.DispatchMessage( |
| R"({"jsonrpc":"2.0","params":{"hello": "world"}})"); |
| |
| EXPECT_EQ(notification_fun_called, 0); |
| EXPECT_EQ(write_fun_called, 1); // Complain about missing method. |
| EXPECT_EQ(dispatcher.exception_count(), 0); |
| } |
| |
| TEST(JsonRpcDispatcherTest, CallNotification) { |
| int write_fun_called = 0; |
| int notification_fun_called = 0; |
| |
| JsonRpcDispatcher dispatcher([&](absl::string_view s) { |
| std::cerr << s; |
| ++write_fun_called; |
| }); |
| const bool registered = |
| dispatcher.AddNotificationHandler("foo", [&](const json &j) { |
| EXPECT_EQ(j, json::parse(R"({ "hello": "world"})")); |
| ++notification_fun_called; |
| }); |
| EXPECT_TRUE(registered); |
| |
| // Registration for method with that name only works once. |
| EXPECT_FALSE(dispatcher.AddNotificationHandler("foo", [](const json &j) {})); |
| |
| dispatcher.DispatchMessage( |
| R"({"jsonrpc":"2.0","method":"foo","params":{"hello": "world"}})"); |
| |
| EXPECT_EQ(notification_fun_called, 1); |
| EXPECT_EQ(write_fun_called, 0); // Notifications don't have responses. |
| EXPECT_EQ(dispatcher.exception_count(), 0); |
| } |
| |
| TEST(JsonRpcDispatcherTest, CallNotification_NotReportInternalError) { |
| int write_fun_called = 0; |
| int notification_fun_called = 0; |
| |
| JsonRpcDispatcher dispatcher( |
| [&](absl::string_view s) { ++write_fun_called; }); |
| |
| // This method does not complete but throws an exception. |
| dispatcher.AddNotificationHandler("foo", [&](const json &j) -> json { |
| ++notification_fun_called; |
| throw std::runtime_error("Okay, Houston, we've had a problem here"); |
| }); |
| |
| dispatcher.DispatchMessage( |
| R"({"jsonrpc":"2.0","method":"foo","params":{"hello":"world"}})"); |
| |
| EXPECT_EQ(notification_fun_called, 1); |
| EXPECT_EQ(write_fun_called, 0); // Notification issues never sent back. |
| EXPECT_EQ(dispatcher.exception_count(), 1); |
| } |
| |
| TEST(JsonRpcDispatcherTest, CallNotification_MissingMethodImplemented) { |
| // A notification whose method is not registered must be silently ignored. |
| // No response with error. |
| int write_fun_called = 0; |
| |
| JsonRpcDispatcher dispatcher([&](absl::string_view s) { // |
| ++write_fun_called; |
| }); |
| |
| dispatcher.DispatchMessage( |
| R"({"jsonrpc":"2.0","method":"foo","params":{"hello": "world"}})"); |
| |
| EXPECT_EQ(write_fun_called, 0); |
| EXPECT_EQ(dispatcher.exception_count(), 0); |
| } |
| |
| TEST(JsonRpcDispatcherTest, CallRpcHandler) { |
| int write_fun_called = 0; |
| int rpc_fun_called = 0; |
| |
| JsonRpcDispatcher dispatcher([&](absl::string_view s) { |
| const json j = json::parse(s); |
| EXPECT_EQ(std::string(j["result"]["some"]), "response"); |
| EXPECT_TRUE(j.find("error") == j.end()); |
| ++write_fun_called; |
| }); |
| const bool registered = |
| dispatcher.AddRequestHandler("foo", [&](const json &j) -> json { |
| EXPECT_EQ(j, json::parse(R"({ "hello":"world"})")); |
| ++rpc_fun_called; |
| return json::parse(R"({ "some": "response"})"); |
| }); |
| EXPECT_TRUE(registered); |
| |
| // Registration with already registered name should fail. |
| EXPECT_FALSE(dispatcher.AddRequestHandler( |
| "foo", [](const json &j) -> json { return nullptr; })); |
| |
| dispatcher.DispatchMessage( |
| R"({"jsonrpc":"2.0","id":1,"method":"foo","params":{"hello":"world"}})"); |
| |
| EXPECT_EQ(rpc_fun_called, 1); |
| EXPECT_EQ(write_fun_called, 1); |
| EXPECT_EQ(dispatcher.exception_count(), 0); |
| } |
| |
| TEST(JsonRpcDispatcherTest, CallRpcHandler_ReportInternalError) { |
| int write_fun_called = 0; |
| int rpc_fun_called = 0; |
| |
| JsonRpcDispatcher dispatcher([&](absl::string_view s) { |
| const json j = json::parse(s); |
| EXPECT_TRUE(j.find("error") != j.end()); |
| EXPECT_EQ(j["error"]["code"], JsonRpcDispatcher::kInternalError) << s; |
| ++write_fun_called; |
| }); |
| |
| // This method does not complete but throws an exception. |
| dispatcher.AddRequestHandler("foo", [&](const json &j) -> json { |
| ++rpc_fun_called; |
| throw std::runtime_error("Okay, Houston, we've had a problem here"); |
| }); |
| |
| dispatcher.DispatchMessage( |
| R"({"jsonrpc":"2.0","id":1,"method":"foo","params":{"hello":"world"}})"); |
| |
| EXPECT_EQ(rpc_fun_called, 1); |
| EXPECT_EQ(write_fun_called, 1); |
| EXPECT_EQ(dispatcher.exception_count(), 1); |
| } |
| |
| TEST(JsonRpcDispatcherTest, CallRpcHandler_MissingMethodImplemented) { |
| int write_fun_called = 0; |
| |
| JsonRpcDispatcher dispatcher([&](absl::string_view s) { |
| const json j = json::parse(s); |
| EXPECT_TRUE(j.find("error") != j.end()); |
| EXPECT_EQ(j["error"]["code"], JsonRpcDispatcher::kMethodNotFound) << s; |
| ++write_fun_called; |
| }); |
| |
| dispatcher.DispatchMessage( |
| R"({"jsonrpc":"2.0","id":1,"method":"foo","params":{"hello":"world"}})"); |
| |
| EXPECT_EQ(write_fun_called, 1); // Reported error. |
| EXPECT_EQ(dispatcher.exception_count(), 0); |
| } |
| |
| TEST(JsonRpcDispatcherTest, SendNotificationToClient) { |
| int write_fun_called = 0; |
| JsonRpcDispatcher dispatcher([&](absl::string_view s) { |
| const json j = json::parse(s); |
| EXPECT_EQ(j["method"], "greeting_method"); |
| EXPECT_EQ(j["params"], "Hi, y'all"); |
| ++write_fun_called; |
| }); |
| |
| const json params = "Hi, y'all"; |
| dispatcher.SendNotification("greeting_method", params); |
| EXPECT_EQ(1, write_fun_called); |
| } |
| } // namespace lsp |
| } // namespace verible |