blob: 11b5eefe2e4ac366b5831d77d41ecde416f2afb5 [file] [log] [blame]
#include "macros.hpp"
#include <memory>
#include <system_error> // NOLINT
#include <string_view>
#include <utility>
#include <string>
#include <gtest/gtest.h>
#include "boost/asio/io_context.hpp" // NOLINT
#include "boost/beast/http/verb.hpp" // NOLINT
#include "app.hpp"
#include "app_singleton.hpp"
#include "http_request.hpp"
#include "async_resp.hpp"
#include "logging.hpp"
#include "utility.hpp"
#include "interface.hpp"
#include "http_response.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include <nlohmann/json.hpp>
namespace redfish_plugin {
int platform_plugins_called = 0; // NOLINT
void loadPlatformPlugins() {
auto callback = [](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
platform_plugins_called++;
};
REDFISH_HANDLER_ADD("/platform_plugins/", boost::beast::http::verb::get,
redfish::privileges::getChassis, std::move(callback));
}
int google_plugins_called = 0; // NOLINT
void loadGooglePlugins() {
auto callback = [](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
google_plugins_called++;
};
REDFISH_HANDLER_ADD("/google_plugins/", boost::beast::http::verb::get,
redfish::privileges::getChassis, std::move(callback));
}
} // namespace redfish_plugin
namespace crow {
namespace {
class PluginMacrosTest : public testing::Test {
public:
PluginMacrosTest() {
io = std::make_shared<boost::asio::io_context>();
app = std::make_unique<crow::App>(/*allowSessionEmpty=*/true, io);
crow::globalBmcWebApp = app.get();
}
private:
std::shared_ptr<boost::asio::io_context> io;
std::unique_ptr<crow::App> app;
};
TEST_F(PluginMacrosTest, AddedHandlerCanBeInvoked) {
int called = 0;
auto callback = [&called](const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
called++;
};
REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
redfish::privileges::getChassis, std::move(callback));
crow::globalBmcWebApp->validate();
constexpr std::string_view url = "/hello/";
std::error_code ec;
Request req{{boost::beast::http::verb::get, url, 11}, ec};
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
crow::globalBmcWebApp->handle(req, asyncResp);
EXPECT_EQ(called, 1);
}
#ifdef PLATFORM_PLUGINS_ENABLED
TEST_F(PluginMacrosTest, AddedRedfishResourceHandlerCanBeInvoked) {
crow::Logger::setLogLevel(crow::LogLevel::Debug);
int called_generic = 0;
BMCWEB_ROUTE((*crow::globalBmcWebApp),
"/redfish/v1/UpdateService/FirmwareInventory/<str>/")
.privileges(redfish::privileges::getSoftwareInventory)
.methods(boost::beast::http::verb::get)(
[&called_generic](
const crow::Request& reqIn,
const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
const std::string& param) {
called_generic++;
std::shared_ptr<std::string> swId =
std::make_shared<std::string>(param);
asyncRespIn->res.jsonValue["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "UpdateService",
"FirmwareInventory", *swId);
BMCWEB_LOG_DEBUG << "DEBUG: Inside generic FW ROUTE for " << *swId;
BMCWEB_LOG_DEBUG << "DEBUG: req method is: "
<< reqIn.methodString();
});
int called_404 = 0;
BMCWEB_ROUTE((*crow::globalBmcWebApp), "/redfish/<path>")
.methodNotAllowed()
.privileges(redfish::privileges::privilegeSetLogin)(
[&called_404](const crow::Request& reqIn,
const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
const std::string& path) {
called_404++;
asyncRespIn->res.jsonValue["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", path);
BMCWEB_LOG_DEBUG << "DEBUG: req method for 404 is: "
<< reqIn.methodString();
});
int called = 0;
auto callback = [&called](const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
called++;
};
REDFISH_HANDLER_ADD("/redfish/v1/UpdateService/FirmwareInventory/SpecialFW/",
boost::beast::http::verb::get,
redfish::privileges::getSoftwareInventory,
std::move(callback));
crow::globalBmcWebApp->validate();
constexpr std::string_view url =
"/redfish/v1/UpdateService/FirmwareInventory/SpecialFW";
std::error_code ec;
Request req{{boost::beast::http::verb::get, url, 11}, ec};
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
crow::globalBmcWebApp->handle(req, asyncResp);
crow::globalBmcWebApp->handle(req, asyncResp);
crow::globalBmcWebApp->handle(req, asyncResp);
EXPECT_EQ(called, 3);
EXPECT_EQ(called_generic, 0);
EXPECT_EQ(called_404, 0);
constexpr std::string_view url2 =
"/redfish/v1/UpdateService/FirmwareInventory/foo";
std::error_code ec2;
Request req2{{boost::beast::http::verb::get, url2, 11}, ec2};
auto asyncResp2 = std::make_shared<bmcweb::AsyncResp>();
crow::globalBmcWebApp->handle(req2, asyncResp2);
crow::globalBmcWebApp->handle(req2, asyncResp2);
crow::globalBmcWebApp->handle(req2, asyncResp2);
crow::globalBmcWebApp->handle(req2, asyncResp2);
crow::globalBmcWebApp->handle(req2, asyncResp2);
EXPECT_EQ(called, 3);
EXPECT_EQ(called_generic, 5);
EXPECT_EQ(called_404, 0);
}
#endif
TEST_F(PluginMacrosTest, DuplicateHandlerThrows) {
auto callback = [](const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {};
REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
redfish::privileges::getChassis, callback);
REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
redfish::privileges::getChassis, std::move(callback));
EXPECT_ANY_THROW(crow::globalBmcWebApp->validate());
}
TEST_F(PluginMacrosTest, ReplacedNonExistHandlerThrows) {
auto callback = [](const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {};
EXPECT_ANY_THROW(REDFISH_HANDLER_REPLACE("/hello/",
boost::beast::http::verb::get,
std::move(callback)););
}
TEST_F(PluginMacrosTest, ReplacedHandlerCanBeInvoked) {
int originalCalled = 0;
auto originalCallback = [&originalCalled](
const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
originalCalled++;
};
int replacedCalled = 0;
auto replacedCallback = [&replacedCalled](
const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
replacedCalled++;
};
REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
redfish::privileges::getChassis,
std::move(originalCallback));
REDFISH_HANDLER_REPLACE("/hello/", boost::beast::http::verb::get,
std::move(replacedCallback));
crow::globalBmcWebApp->validate();
constexpr std::string_view url = "/hello/";
std::error_code ec;
Request req{{boost::beast::http::verb::get, url, 11}, ec};
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
crow::globalBmcWebApp->handle(req, asyncResp);
EXPECT_EQ(originalCalled, 0);
EXPECT_EQ(replacedCalled, 1);
}
TEST_F(PluginMacrosTest, RemoveNonExistHandlerThrows) {
EXPECT_ANY_THROW(
REDFISH_HANDLER_REMOVE("/hello/", boost::beast::http::verb::get));
}
TEST_F(PluginMacrosTest, RemovedHandlerNotBeInvoked) {
int originalCalled = 0;
auto originalCallback = [&originalCalled](
const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
originalCalled++;
};
REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
redfish::privileges::getChassis,
std::move(originalCallback));
REDFISH_HANDLER_REMOVE("/hello/", boost::beast::http::verb::get);
crow::globalBmcWebApp->validate();
constexpr std::string_view url = "/hello/";
std::error_code ec;
Request req{{boost::beast::http::verb::get, url, 11}, ec};
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
crow::globalBmcWebApp->handle(req, asyncResp);
EXPECT_EQ(originalCalled, 0);
}
TEST_F(PluginMacrosTest, AppendNonExistHandlerThrows) {
auto callback = [](const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {};
EXPECT_ANY_THROW(REDFISH_HANDLER_APPEND(
"/hello/", boost::beast::http::verb::get, std::move(callback)));
}
TEST_F(PluginMacrosTest, AppendRemovedHandlerThrows) {
int originalCalled = 0;
auto originalCallback = [&originalCalled](
const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
originalCalled++;
};
int appendedCalled = 0;
auto appendedCallback = [&appendedCalled](
const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
appendedCalled++;
};
REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
redfish::privileges::getChassis,
std::move(originalCallback));
REDFISH_HANDLER_APPEND("/hello/", boost::beast::http::verb::get,
std::move(appendedCallback));
REDFISH_HANDLER_REMOVE("/hello/", boost::beast::http::verb::get);
constexpr std::string_view url = "/hello/";
std::error_code ec;
Request req{{boost::beast::http::verb::get, url, 11}, ec};
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
crow::globalBmcWebApp->handle(req, asyncResp);
EXPECT_EQ(originalCalled, 0);
EXPECT_EQ(appendedCalled, 0);
}
TEST_F(PluginMacrosTest, AppendedHandlerCanBeCompletelyRemoved) {
auto callback = [](const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {};
REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
redfish::privileges::getChassis, callback);
REDFISH_HANDLER_REMOVE("/hello/", boost::beast::http::verb::get);
EXPECT_ANY_THROW(REDFISH_HANDLER_APPEND(
"/hello/", boost::beast::http::verb::get, std::move(callback)));
}
TEST_F(PluginMacrosTest, ReplacedHandlerCanBeAppended) {
int replacedCalled = 0;
auto replacedCallback = [&replacedCalled](
const Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& resp) {
EXPECT_TRUE(redfish::setUpRedfishRouteOnGlobalApp(req, resp));
replacedCalled++;
};
int appendedCalled = 0;
auto appendedCallback = [&appendedCalled](
const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
appendedCalled++;
};
REDFISH_HANDLER_ADD(
"/hello/", boost::beast::http::verb::get, redfish::privileges::getChassis,
[](const Request&, const std::shared_ptr<bmcweb::AsyncResp>&) {});
REDFISH_HANDLER_REPLACE("/hello/", boost::beast::http::verb::get,
std::move(replacedCallback));
REDFISH_HANDLER_APPEND("/hello/", boost::beast::http::verb::get,
std::move(appendedCallback));
crow::globalBmcWebApp->validate();
constexpr std::string_view url = "/hello/";
std::error_code ec;
Request req{{boost::beast::http::verb::get, url, 11}, ec};
// This is to make sure |asyncResp| destructs
{
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
crow::globalBmcWebApp->handle(req, asyncResp);
}
EXPECT_EQ(appendedCalled, 1);
EXPECT_EQ(replacedCalled, 1);
}
// The test makes sure the execution order is:
// 1. originalCallback
// 2. appendedCallback (if multiple appends exist, the order is LIFO; please
// note multiple appends should be rarely used)
// 3. callback setup by setUpRedfishRoute
TEST_F(PluginMacrosTest, AppendedHandlerBeInvokedInOrder) {
crow::Logger::setLogLevel(crow::LogLevel::Debug);
int originalCalled = 0;
int appendedOneCalled = 0;
int appendedTwoCalled = 0;
int onlyCalled = 0;
auto originalCallback =
[&originalCalled, &appendedOneCalled, &appendedTwoCalled, &onlyCalled](
const Request& req, const std::shared_ptr<bmcweb::AsyncResp>& resp) {
EXPECT_TRUE(redfish::setUpRedfishRouteOnGlobalApp(req, resp));
EXPECT_EQ(appendedOneCalled, 0);
EXPECT_EQ(appendedTwoCalled, 0);
EXPECT_EQ(onlyCalled, 0);
originalCalled++;
resp->res.jsonValue = R"(
{
"Members": [
{
"@odata.id": "/only"
}
]
}
)"_json;
};
auto appendedOneCallback =
[&originalCalled, &appendedOneCalled, &appendedTwoCalled, &onlyCalled](
const Request&, const std::shared_ptr<bmcweb::AsyncResp>&) {
EXPECT_EQ(originalCalled, 1);
// this tests that the callback setup by setUpRedfishRoute hasn't run
EXPECT_EQ(onlyCalled, 0);
// the order is LIFO
EXPECT_EQ(appendedTwoCalled, 1);
appendedOneCalled++;
};
auto appendedTwoCallback =
[&originalCalled, &appendedOneCalled, &appendedTwoCalled, &onlyCalled](
const Request&, const std::shared_ptr<bmcweb::AsyncResp>&) {
EXPECT_EQ(originalCalled, 1);
// this tests that the callback setup by setUpRedfishRoute hasn't run
EXPECT_EQ(onlyCalled, 0);
EXPECT_EQ(appendedOneCalled, 0);
appendedTwoCalled++;
};
auto onlyCallback = [&originalCalled, &appendedOneCalled, &appendedTwoCalled,
&onlyCalled](const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
EXPECT_TRUE(originalCalled);
EXPECT_TRUE(appendedOneCalled);
EXPECT_TRUE(appendedTwoCalled);
onlyCalled = 1;
};
REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
redfish::privileges::getChassis,
std::move(originalCallback));
REDFISH_HANDLER_ADD("/only", boost::beast::http::verb::get,
redfish::privileges::getChassis, std::move(onlyCallback));
REDFISH_HANDLER_APPEND("/hello/", boost::beast::http::verb::get,
std::move(appendedOneCallback));
REDFISH_HANDLER_APPEND("/hello/", boost::beast::http::verb::get,
std::move(appendedTwoCallback));
crow::globalBmcWebApp->validate();
constexpr std::string_view url = "/hello/?only";
std::error_code ec;
Request req{{boost::beast::http::verb::get, url, 11}, ec};
// This is to make sure |asyncResp| destructs
{
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
asyncResp->res.setCompleteRequestHandler([](crow::Response&) {});
crow::globalBmcWebApp->handle(req, asyncResp);
}
EXPECT_EQ(onlyCalled, 1);
}
TEST_F(PluginMacrosTest, AppendedHandlerGetsParams) {
auto originalCallback = [](const Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& resp,
const std::string&) {
EXPECT_TRUE(redfish::setUpRedfishRouteOnGlobalApp(req, resp));
};
int appendedCalled = 0;
auto appendedCallback = [&appendedCalled](
const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&,
const std::string& param) {
EXPECT_EQ(param, "awesome");
appendedCalled++;
};
REDFISH_HANDLER_ADD("/hello/<str>/", boost::beast::http::verb::get,
redfish::privileges::getChassis,
std::move(originalCallback));
REDFISH_HANDLER_APPEND("/hello/<str>/", boost::beast::http::verb::get,
std::move(appendedCallback));
crow::globalBmcWebApp->validate();
constexpr std::string_view url = "/hello/awesome/";
std::error_code ec;
Request req{{boost::beast::http::verb::get, url, 11}, ec};
// This is to make sure |asyncResp| destructs
{
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
asyncResp->res.setCompleteRequestHandler([](crow::Response&) {});
crow::globalBmcWebApp->handle(req, asyncResp);
}
EXPECT_EQ(appendedCalled, 1);
}
// Every Redfish Route is suppose to have Redfish parameter handler; however,
// plugin code shouldn't fail if a Redfish route is not configured properly.
// This test ensures that scenario.
TEST_F(PluginMacrosTest, AppendedHandlerRunsWithoutRedfishParamsHandler) {
auto originalCallback = [](const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {};
int appendedCalled = 0;
auto appendedCallback = [&appendedCalled](
const Request&,
const std::shared_ptr<bmcweb::AsyncResp>&) {
appendedCalled++;
};
REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
redfish::privileges::getChassis,
std::move(originalCallback));
REDFISH_HANDLER_APPEND("/hello/", boost::beast::http::verb::get,
std::move(appendedCallback));
crow::globalBmcWebApp->validate();
constexpr std::string_view url = "/hello/";
std::error_code ec;
Request req{{boost::beast::http::verb::get, url, 11}, ec};
// This is to make sure |asyncResp| destructs
{
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
asyncResp->res.setCompleteRequestHandler([](crow::Response&) {});
crow::globalBmcWebApp->handle(req, asyncResp);
}
EXPECT_EQ(appendedCalled, 1);
}
TEST_F(PluginMacrosTest, AddHandlerWorksWithArbitraryLayerOfPlugins) {
redfish_plugin::loadPlatformPlugins();
redfish_plugin::loadGooglePlugins();
crow::globalBmcWebApp->validate();
std::error_code ec;
Request req1{{boost::beast::http::verb::get, "/platform_plugins/", 11}, ec};
auto asyncResp1 = std::make_shared<bmcweb::AsyncResp>();
crow::globalBmcWebApp->handle(req1, asyncResp1);
Request req2{{boost::beast::http::verb::get, "/google_plugins/", 11}, ec};
auto asyncResp2 = std::make_shared<bmcweb::AsyncResp>();
crow::globalBmcWebApp->handle(req2, asyncResp2);
EXPECT_EQ(redfish_plugin::platform_plugins_called, 1);
EXPECT_EQ(redfish_plugin::google_plugins_called, 1);
}
TEST_F(PluginMacrosTest, AppendHandlerCanGetDelegateExpandLevelOfQuery) {
constexpr int expandLevelInGenericHandler = 6;
BMCWEB_ROUTE((*crow::globalBmcWebApp),
"/redfish/v1/Systems/system/Processors/")
.privileges(redfish::privileges::headProcessorCollection)
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
asyncResp->delegatedExpandLevel = expandLevelInGenericHandler;
});
int delegatedExpandLevelInAppendHandler = 0;
auto appendedCallback =
[&delegatedExpandLevelInAppendHandler](
const Request&, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
delegatedExpandLevelInAppendHandler = asyncResp->delegatedExpandLevel;
};
REDFISH_HANDLER_APPEND("/redfish/v1/Systems/system/Processors/",
boost::beast::http::verb::get,
std::move(appendedCallback));
crow::globalBmcWebApp->validate();
constexpr std::string_view url = "/redfish/v1/Systems/system/Processors/";
std::error_code ec;
Request req{{boost::beast::http::verb::get, url, 11}, ec};
// This is to make sure |asyncResp| destructs
{
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
asyncResp->res.setCompleteRequestHandler([](crow::Response&) {});
crow::globalBmcWebApp->handle(req, asyncResp);
}
EXPECT_EQ(delegatedExpandLevelInAppendHandler, expandLevelInGenericHandler);
}
TEST_F(PluginMacrosTest, AppendHandlerCaptureByReferenceCheck) {
// check the safety of pass by copy for callback handler
boost::asio::io_context io;
auto originalCallback =
[&io](const Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
boost::asio::post(
io, [asyncResp]() { BMCWEB_LOG_DEBUG << "Delay the call \n"; });
EXPECT_EQ(req.target(), "/hello");
};
auto appendedCallback = [](const Request& req,
const std::shared_ptr<bmcweb::AsyncResp>&) {
EXPECT_EQ(req.target(), "/hello");
};
REDFISH_HANDLER_ADD("/hello/", boost::beast::http::verb::get,
redfish::privileges::getChassis,
std::move(originalCallback));
REDFISH_HANDLER_APPEND("/hello/", boost::beast::http::verb::get,
std::move(appendedCallback));
crow::globalBmcWebApp->validate();
constexpr std::string_view url = "/hello";
std::error_code ec;
// This is to make sure |asyncResp| destructs
{
Request req{{boost::beast::http::verb::get, url, 11}, ec};
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
asyncResp->res.setCompleteRequestHandler([](crow::Response&) {});
crow::globalBmcWebApp->handle(req, asyncResp);
}
io.run_one();
}
} // namespace
} // namespace crow