blob: 3ee246eab88460c9ffa483b5f0de9d2dc98ff62e [file]
#include <fstream>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include <gmock/gmock.h> // IWYU pragma: keep
#include <gtest/gtest.h> // IWYU pragma: keep
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "boost/asio/io_context.hpp" // NOLINT
#include "boost/beast/http/status.hpp" // NOLINT
#include "boost/beast/http/verb.hpp" // NOLINT
#include "redfish_devpath_injector.hpp"
#include "app.hpp"
#include "app_singleton.hpp"
#include "http_request.hpp"
#include "http_response.hpp"
#include "routing.hpp"
#include "async_resp.hpp"
#include "macros.hpp"
#include "query.hpp"
#include <nlohmann/json.hpp>
// IWYU pragma: no_include <gtest/gtest-message.h>
// IWYU pragma: no_include <gtest/gtest-test-part.h>
// IWYU pragma: no_include "gtest/gtest_pred_impl.h"
// IWYU pragma: no_include <gmock/gmock-matchers.h>
// IWYU pragma: no_include <gmock/gmock-more-matchers.h>
// IWYU pragma: no_include <gtest/gtest-matchers.h>
namespace crow {
namespace {
std::string WriteTmpFile(std::string_view name, std::string_view content) {
std::string path = absl::StrCat(testing::TempDir(), "/", name);
std::ofstream out(path);
out << content;
out.close();
return path;
}
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Pointee;
using ::testing::UnorderedElementsAre;
TEST(GetRoutes, TestEmptyRoutes) {
App app(/*allowSessionEmpty=*/true);
app.validate();
EXPECT_THAT(app.getRoutes(), IsEmpty());
}
// Tests that static urls are correctly passed
TEST(GetRoutes, TestOneRoute) {
App app(/*allowSessionEmpty=*/true);
BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
// TODO: "/" doesn't get reported in |getRoutes| today. Uncomment this once
// it is fixed
// EXPECT_THAT(app.getRoutes(),
// testing::ElementsAre(Pointee(Eq("/"))));
}
// Tests that static urls are correctly passed
TEST(GetRoutes, TestlotsOfRoutes) {
App app(/*allowSessionEmpty=*/true);
BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
BMCWEB_ROUTE(app, "/foo")([]() { return boost::beast::http::status::ok; });
BMCWEB_ROUTE(app, "/bar")([]() { return boost::beast::http::status::ok; });
BMCWEB_ROUTE(app, "/baz")([]() { return boost::beast::http::status::ok; });
BMCWEB_ROUTE(app, "/boo")([]() { return boost::beast::http::status::ok; });
BMCWEB_ROUTE(app, "/moo")([]() { return boost::beast::http::status::ok; });
app.validate();
// TODO: "/" doesn't get reported in |getRoutes| today. Uncomment this once
// it is fixed
EXPECT_THAT(app.getRoutes(),
UnorderedElementsAre(
// Pointee(Eq("/")),
Pointee(Eq("/foo")), Pointee(Eq("/bar")), Pointee(Eq("/baz")),
Pointee(Eq("/boo")), Pointee(Eq("/moo"))));
}
} // namespace
TEST(AppDevpathInjectorTest, InjectExpandRequests) {
std::string config_content = R"pb(
redfish_to_uhm_mapping {
key { redfish_url_pattern: "/redfish/v1/Chassis/ChassisA" }
uhm_monitored_component_key { barepath: "/phys/ChassisA" }
redfish_expose_config {
expose_oem_google_devpath: true
set_plc_as_placeholder: false
}
}
redfish_to_uhm_mapping {
key {
redfish_url_pattern:
"/redfish/v1/Chassis/ChassisA/ThermalSubsystem/Fans/fan0"
}
uhm_monitored_component_key { barepath: "/phys/ChassisA/fan0" }
redfish_expose_config {
expose_oem_google_devpath: true
set_plc_as_placeholder: false
}
}
)pb";
std::string config_path =
WriteTmpFile("app_expand_config.textproto", config_content);
// Instantiate the injector at the main/test function level
absl::StatusOr<std::unique_ptr<
devpath_plugin::RedfishDevpathInjector>> injector =
devpath_plugin::RedfishDevpathInjector::Create(config_path);
ASSERT_TRUE(injector.ok());
// Initialize App and pass the raw pointer of the injector
auto io =
std::make_shared<boost::asio::io_context>();
App app(true, io, nullptr, (*injector).get());
ASSERT_EQ(app.GetDevpathInjector(), (*injector).get());
crow::globalBmcWebApp = &app;
// Track if sub-route plugins ran
bool chassis_plugin_called = false;
bool fan_plugin_called = false;
// Register routes on the app.
BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>")
.methods(boost::beast::http::verb::get)(
[&app](const Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& /*chassisId*/) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
asyncResp->res.jsonValue = {
{"@odata.id", "/redfish/v1/Chassis/ChassisA"},
{"Id", "ChassisA"},
{"Name", "ChassisA"},
{"ThermalSubsystem",
{{"@odata.id",
"/redfish/v1/Chassis/ChassisA/ThermalSubsystem"}}}
};
});
// Use REDFISH_HANDLER_APPEND to inject a plugin on the chassis route
REDFISH_HANDLER_APPEND(
"/redfish/v1/Chassis/<str>", boost::beast::http::verb::get,
[&chassis_plugin_called](
const Request& /*req*/,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& /*chassisId*/) {
chassis_plugin_called = true;
asyncResp->res.jsonValue["ChassisPluginIndicator"] =
"chassis_plugin_worked";
});
BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem")
.methods(boost::beast::http::verb::get)(
[&app](const Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& /*chassisId*/) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
asyncResp->res.jsonValue = {
{"@odata.id", "/redfish/v1/Chassis/ChassisA/ThermalSubsystem"},
{"Id", "ThermalSubsystem"},
{"Name", "Thermal Subsystem"},
{"Fans",
{{"@odata.id",
"/redfish/v1/Chassis/ChassisA/ThermalSubsystem/Fans"}}}
};
});
BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans")
.methods(boost::beast::http::verb::get)(
[&app](const Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& /*chassisId*/) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
asyncResp->res.jsonValue = {
{"@odata.id",
"/redfish/v1/Chassis/ChassisA/ThermalSubsystem/Fans"},
{"Members",
{{{"@odata.id",
"/redfish/v1/Chassis/ChassisA/ThermalSubsystem/Fans/"
"fan0"}}}},
{"Members@odata.count", 1},
{"Name", "Fan Collection"}
};
});
BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>")
.methods(boost::beast::http::verb::get)(
[&app](const Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& /*chassisId*/,
const std::string& /*fanId*/) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
asyncResp->res.jsonValue = {
{"@odata.id",
"/redfish/v1/Chassis/ChassisA/ThermalSubsystem/Fans/fan0"},
{"Id", "fan0"},
{"Name", "fan0"}
};
});
// Use REDFISH_HANDLER_APPEND to inject a plugin on the fan endpoint
REDFISH_HANDLER_APPEND(
"/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>",
boost::beast::http::verb::get,
[&fan_plugin_called](
const Request& /*req*/,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& /*chassisId*/, const std::string& /*fanId*/) {
fan_plugin_called = true;
asyncResp->res.jsonValue["FanPluginIndicator"] =
"fan_plugin_worked";
});
app.validate();
// Create expand request
std::error_code ec;
Request req(
{boost::beast::http::verb::get,
"/redfish/v1/Chassis/ChassisA?$expand=*($levels=3)", 11},
ec);
ASSERT_FALSE(ec);
// Capture final response
nlohmann::json final_response_json;
boost::beast::http::status final_status =
boost::beast::http::status::unknown;
bool completed = false;
{
auto asyncResp =
std::make_shared<bmcweb::AsyncResp>();
asyncResp->res.setCompleteRequestHandler(
[&final_response_json, &final_status, &completed](Response& res) {
final_response_json = res.jsonValue;
final_status = res.result();
completed = true;
});
app.handle(req, asyncResp);
} // asyncResp count drops to 0, destructor is invoked, executing all
// query param and injector logic.
ASSERT_TRUE(completed);
EXPECT_TRUE(chassis_plugin_called);
EXPECT_TRUE(fan_plugin_called);
EXPECT_EQ(final_status, boost::beast::http::status::ok);
nlohmann::json expected = nlohmann::json::parse(R"json(
{
"@odata.id": "/redfish/v1/Chassis/ChassisA",
"ChassisPluginIndicator": "chassis_plugin_worked",
"Id": "ChassisA",
"Location": {
"Oem": {
"Google": {
"Devpath": "/phys/ChassisA"
}
},
"PartLocation": {
"ServiceLabel": "ChassisA"
}
},
"Name": "ChassisA",
"ThermalSubsystem": {
"@odata.id": "/redfish/v1/Chassis/ChassisA/ThermalSubsystem",
"Fans": {
"@odata.id": "/redfish/v1/Chassis/ChassisA/ThermalSubsystem/Fans",
"Members": [
{
"@odata.id": "/redfish/v1/Chassis/ChassisA/ThermalSubsystem/Fans/fan0",
"FanPluginIndicator": "fan_plugin_worked",
"Id": "fan0",
"Location": {
"Oem": {
"Google": {
"Devpath": "/phys/ChassisA/fan0"
}
},
"PartLocation": {
"ServiceLabel": "fan0"
},
"PartLocationContext": "ChassisA"
},
"Name": "fan0"
}
],
"Members@odata.count": 1,
"Name": "Fan Collection"
},
"Id": "ThermalSubsystem",
"Name": "Thermal Subsystem"
}
}
)json");
EXPECT_EQ(final_response_json, expected);
}
} // namespace crow