blob: 9968230cdfb20a84899e8b254f53398b7b2af910 [file] [log] [blame] [edit]
// 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