blob: d91be406051b6ec9a7c533554d2de939ea72160b [file] [log] [blame]
#include "app.hpp"
#include "app_singleton.hpp"
#include "async_resp.hpp"
#include "http_request.hpp"
#include "interface.hpp"
#include "macros.hpp"
#include "privileges.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "utility.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/beast/http/verb.hpp>
#include <nlohmann/json.hpp>
#include <memory>
#include <string_view>
#include <gtest/gtest.h>
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