| #include "redfish_devpath_injector.hpp" | |
| #include <fstream> | |
| #include <memory> | |
| #include <string> | |
| #include <string_view> | |
| #include <system_error> // NOLINT | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "boost/beast/http/status.hpp" // NOLINT | |
| #include "boost/beast/http/verb.hpp" // NOLINT | |
| #include "http_request.hpp" | |
| #include "http_response.hpp" | |
| #include <nlohmann/json.hpp> | |
| using ::testing::status::StatusIs; | |
| namespace devpath_plugin { | |
| 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; | |
| } | |
| crow::Request CreateRequest( | |
| std::string_view path, | |
| boost::beast::http::verb method = boost::beast::http::verb::get) { | |
| std::error_code ec; | |
| crow::Request req(std::string_view(""), ec); | |
| req.set_method(method); | |
| req.target(path); | |
| return req; | |
| } | |
| TEST(RedfishDevpathInjectorTest, LoadValidConfigSuccess) { | |
| std::string config_content = R"pb( | |
| redfish_to_uhm_mapping { | |
| key { redfish_url_pattern: "/redfish/v1/RootOfTrustCollection/RotA" } | |
| uhm_monitored_component_key { barepath: "/phys/BoardA:device:rot_a" } | |
| redfish_expose_config { | |
| expose_oem_google_devpath: true | |
| set_plc_as_placeholder: false | |
| embedded_location_context: "rot_context_a" | |
| } | |
| } | |
| )pb"; | |
| std::string config_path = | |
| WriteTmpFile("valid_config.textproto", config_content); | |
| ASSERT_OK(RedfishDevpathInjector::Create(config_path).status()); | |
| } | |
| TEST(RedfishDevpathInjectorTest, LoadInvalidConfigReturnsError) { | |
| std::string invalid_config_path = | |
| WriteTmpFile("invalid_config.textproto", "invalid_content"); | |
| EXPECT_THAT(RedfishDevpathInjector::Create(invalid_config_path), | |
| StatusIs(absl::StatusCode::kInvalidArgument)); | |
| } | |
| TEST(RedfishDevpathInjectorTest, NonExistentConfigReturnsError) { | |
| EXPECT_THAT( | |
| RedfishDevpathInjector::Create("/path/to/non_existent_file.textproto"), | |
| StatusIs(absl::StatusCode::kNotFound)); | |
| } | |
| TEST(RedfishDevpathInjectorTest, InjectValidMappingSuccess) { | |
| std::string config_content = R"pb( | |
| redfish_to_uhm_mapping { | |
| key { redfish_url_pattern: "/redfish/v1/RootOfTrustCollection/RotA" } | |
| uhm_monitored_component_key { barepath: "/phys/BoardA:device:rot_a" } | |
| redfish_expose_config { | |
| expose_oem_google_devpath: true | |
| set_plc_as_placeholder: false | |
| embedded_location_context: "rot_context_a" | |
| } | |
| } | |
| redfish_to_uhm_mapping { | |
| key { redfish_url_pattern: "/redfish/v1/Chassis/ChassisA" } | |
| uhm_monitored_component_key { barepath: "/phys/BoardA" } | |
| redfish_expose_config { | |
| expose_oem_google_devpath: false | |
| set_plc_as_placeholder: true | |
| } | |
| } | |
| redfish_to_uhm_mapping { | |
| key { redfish_url_pattern: "/redfish/v1/Chassis/ChassisB" } | |
| uhm_monitored_component_key { barepath: "/phys/BoardA" } | |
| redfish_expose_config { | |
| expose_oem_google_devpath: false | |
| set_plc_as_placeholder: false | |
| } | |
| } | |
| redfish_to_uhm_mapping { | |
| key { redfish_url_pattern: "/redfish/v1/Chassis/ChassisA/Drives/Drive0" } | |
| uhm_monitored_component_key { barepath: "/phys/BoardA/SLOT0" } | |
| redfish_expose_config { | |
| expose_oem_google_devpath: true | |
| set_plc_as_placeholder: false | |
| use_physical_location: true | |
| location_type: "Slot" | |
| } | |
| } | |
| )pb"; | |
| std::string config_path = | |
| WriteTmpFile("injectable_config.textproto", config_content); | |
| absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector = | |
| RedfishDevpathInjector::Create(config_path); | |
| ASSERT_OK(injector.status()); | |
| // Test Component 1 with :device: | |
| { | |
| crow::Request req = CreateRequest("/redfish/v1/RootOfTrustCollection/RotA"); | |
| crow::Response res; | |
| res.result(boost::beast::http::status::ok); | |
| (*injector)->Inject(req, res); | |
| const nlohmann::json& json = res.jsonValue; | |
| EXPECT_EQ(json["Location"]["PartLocation"]["ServiceLabel"], "rot_a"); | |
| EXPECT_EQ(json["Location"]["PartLocationContext"], "BoardA"); | |
| EXPECT_EQ(json["Location"]["Oem"]["Google"]["Devpath"], | |
| "/phys/BoardA:device:rot_a"); | |
| EXPECT_EQ(json["Location"]["Oem"]["Google"]["EmbeddedLocationContext"], | |
| "rot_context_a"); | |
| } | |
| // Test Component 2 without :device: and with placeholder plc | |
| { | |
| crow::Request req = CreateRequest("/redfish/v1/Chassis/ChassisA"); | |
| crow::Response res; | |
| res.result(boost::beast::http::status::ok); | |
| (*injector)->Inject(req, res); | |
| nlohmann::json json = res.jsonValue; | |
| EXPECT_EQ(json["Location"]["PartLocation"]["ServiceLabel"], "BoardA"); | |
| EXPECT_EQ(json["Location"]["PartLocationContext"], | |
| "PlaceholderAndShouldNotBeUsed"); | |
| EXPECT_FALSE(json["Location"]["Oem"]["Google"].contains("Devpath")); | |
| EXPECT_FALSE( | |
| json["Location"]["Oem"]["Google"].contains("EmbeddedLocationContext")); | |
| } | |
| // Test Component 3 where PLC is empty and shouldn't appear | |
| { | |
| crow::Request req = CreateRequest("/redfish/v1/Chassis/ChassisB"); | |
| crow::Response res; | |
| res.result(boost::beast::http::status::ok); | |
| (*injector)->Inject(req, res); | |
| nlohmann::json json = res.jsonValue; | |
| EXPECT_EQ(json["Location"]["PartLocation"]["ServiceLabel"], "BoardA"); | |
| EXPECT_FALSE(json["Location"].contains("PartLocationContext")); | |
| } | |
| // Test Component 4 with use_physical_location enabled | |
| { | |
| crow::Request req = | |
| CreateRequest("/redfish/v1/Chassis/ChassisA/Drives/Drive0"); | |
| crow::Response res; | |
| res.result(boost::beast::http::status::ok); | |
| (*injector)->Inject(req, res); | |
| EXPECT_EQ(res.jsonValue, nlohmann::json::parse(R"json( | |
| { | |
| "PhysicalLocation": { | |
| "Oem": { | |
| "Google": { | |
| "Devpath": "/phys/BoardA/SLOT0" | |
| } | |
| }, | |
| "PartLocation": { | |
| "LocationType": "Slot", | |
| "ServiceLabel": "SLOT0" | |
| }, | |
| "PartLocationContext": "BoardA" | |
| } | |
| } | |
| )json")); | |
| } | |
| } | |
| TEST(RedfishDevpathInjectorTest, | |
| InjectPhysicalLocationOverridesPreexistingFields) { | |
| std::string config_content = R"pb( | |
| redfish_to_uhm_mapping { | |
| key { redfish_url_pattern: "/redfish/v1/Chassis/ChassisA/Drives/Drive1" } | |
| uhm_monitored_component_key { barepath: "/phys/HPM0/DOWNLINK/PE1A/SLOT2" } | |
| redfish_expose_config { | |
| expose_oem_google_devpath: true | |
| set_plc_as_placeholder: false | |
| use_physical_location: true | |
| } | |
| } | |
| redfish_to_uhm_mapping { | |
| key { redfish_url_pattern: "/redfish/v1/Chassis/ChassisA/Drives/Drive2" } | |
| uhm_monitored_component_key { barepath: "/phys/SLOT0" } | |
| redfish_expose_config { | |
| expose_oem_google_devpath: false | |
| set_plc_as_placeholder: false | |
| use_physical_location: true | |
| } | |
| } | |
| )pb"; | |
| std::string config_path = | |
| WriteTmpFile("override_config.textproto", config_content); | |
| absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector = | |
| RedfishDevpathInjector::Create(config_path); | |
| ASSERT_OK(injector.status()); | |
| // Case 1: Pre-existing wrong Devpath and placeholder PLC from handler are | |
| // overwritten when expose_oem_google_devpath is true. | |
| { | |
| crow::Request req = | |
| CreateRequest("/redfish/v1/Chassis/ChassisA/Drives/Drive1"); | |
| crow::Response res; | |
| res.result(boost::beast::http::status::ok); | |
| res.jsonValue = nlohmann::json::parse(R"json( | |
| { | |
| "PhysicalLocation": { | |
| "Oem": { | |
| "Google": { | |
| "Devpath": "/phys/HPM0/DOWNLINK/PE1A/DOWNLINK" | |
| } | |
| }, | |
| "PartLocationContext": "PlaceHolderAndShouldNotBeUsed" | |
| } | |
| } | |
| )json"); | |
| (*injector)->Inject(req, res); | |
| EXPECT_EQ(res.jsonValue, nlohmann::json::parse(R"json( | |
| { | |
| "PhysicalLocation": { | |
| "Oem": { | |
| "Google": { | |
| "Devpath": "/phys/HPM0/DOWNLINK/PE1A/SLOT2" | |
| } | |
| }, | |
| "PartLocation": { | |
| "ServiceLabel": "SLOT2" | |
| }, | |
| "PartLocationContext": "HPM0/DOWNLINK/PE1A" | |
| } | |
| } | |
| )json")); | |
| } | |
| // Case 2: Pre-existing placeholder PLC is erased when part_location_context | |
| // is empty and set_plc_as_placeholder is false. | |
| { | |
| crow::Request req = | |
| CreateRequest("/redfish/v1/Chassis/ChassisA/Drives/Drive2"); | |
| crow::Response res; | |
| res.result(boost::beast::http::status::ok); | |
| res.jsonValue = nlohmann::json::parse(R"json( | |
| { | |
| "PhysicalLocation": { | |
| "PartLocationContext": "PlaceHolderAndShouldNotBeUsed" | |
| } | |
| } | |
| )json"); | |
| (*injector)->Inject(req, res); | |
| EXPECT_EQ(res.jsonValue, nlohmann::json::parse(R"json( | |
| { | |
| "PhysicalLocation": { | |
| "PartLocation": { | |
| "ServiceLabel": "SLOT0" | |
| } | |
| } | |
| } | |
| )json")); | |
| } | |
| } | |
| TEST(RedfishDevpathInjectorTest, InjectNonGetRequestReturnsNoOp) { | |
| std::string config_content = R"pb( | |
| redfish_to_uhm_mapping { | |
| key { redfish_url_pattern: "/redfish/v1/RootOfTrustCollection/RotA" } | |
| uhm_monitored_component_key { barepath: "/phys/BoardA:device:rot_a" } | |
| } | |
| )pb"; | |
| std::string config_path = | |
| WriteTmpFile("nonget_config.textproto", config_content); | |
| absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector = | |
| RedfishDevpathInjector::Create(config_path); | |
| ASSERT_OK(injector.status()); | |
| crow::Request req = CreateRequest("/redfish/v1/RootOfTrustCollection/RotA", | |
| boost::beast::http::verb::post); | |
| crow::Response res; | |
| res.result(boost::beast::http::status::ok); | |
| (*injector)->Inject(req, res); | |
| EXPECT_TRUE(res.jsonValue.empty()); | |
| } | |
| TEST(RedfishDevpathInjectorTest, InjectNonOkStatusReturnsNoOp) { | |
| std::string config_content = R"pb( | |
| redfish_to_uhm_mapping { | |
| key { redfish_url_pattern: "/redfish/v1/RootOfTrustCollection/RotA" } | |
| uhm_monitored_component_key { barepath: "/phys/BoardA:device:rot_a" } | |
| } | |
| )pb"; | |
| std::string config_path = | |
| WriteTmpFile("nonok_config.textproto", config_content); | |
| absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector = | |
| RedfishDevpathInjector::Create(config_path); | |
| ASSERT_OK(injector.status()); | |
| crow::Request req = CreateRequest("/redfish/v1/RootOfTrustCollection/RotA"); | |
| crow::Response res; | |
| res.result(boost::beast::http::status::not_found); | |
| (*injector)->Inject(req, res); | |
| EXPECT_TRUE(res.jsonValue.empty()); | |
| } | |
| TEST(RedfishDevpathInjectorTest, InjectNonExistentUrlReturnsNoOp) { | |
| std::string config_content = R"pb( | |
| redfish_to_uhm_mapping { | |
| key { redfish_url_pattern: "/redfish/v1/RootOfTrustCollection/RotA" } | |
| uhm_monitored_component_key { barepath: "/phys/BoardA:device:rot_a" } | |
| } | |
| )pb"; | |
| std::string config_path = | |
| WriteTmpFile("nonexistent_config.textproto", config_content); | |
| absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector = | |
| RedfishDevpathInjector::Create(config_path); | |
| ASSERT_OK(injector.status()); | |
| crow::Request req = | |
| CreateRequest("/redfish/v1/RootOfTrustCollection/DeviceB"); | |
| crow::Response res; | |
| res.result(boost::beast::http::status::ok); | |
| (*injector)->Inject(req, res); | |
| EXPECT_TRUE(res.jsonValue.empty()); | |
| } | |
| TEST(RedfishDevpathInjectorTest, InjectRegexMappingSuccess) { | |
| std::string config_content = R"pb( | |
| redfish_to_uhm_mapping { | |
| key { | |
| redfish_url_pattern: "/redfish/v1/Systems/system/Storage/StorageModuleA/Controllers/[0-9]+" | |
| } | |
| uhm_monitored_component_key { | |
| barepath: "/phys/SlotA:device:controller_a" | |
| } | |
| redfish_expose_config { | |
| expose_oem_google_devpath: true | |
| set_plc_as_placeholder: false | |
| embedded_location_context: "controller:0" | |
| } | |
| } | |
| )pb"; | |
| std::string config_path = | |
| WriteTmpFile("regex_config.textproto", config_content); | |
| absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector = | |
| RedfishDevpathInjector::Create(config_path); | |
| ASSERT_OK(injector.status()); | |
| // Test correct match | |
| { | |
| crow::Request req = CreateRequest( | |
| "/redfish/v1/Systems/system/Storage/StorageModuleA/Controllers/0"); | |
| crow::Response res; | |
| res.result(boost::beast::http::status::ok); | |
| (*injector)->Inject(req, res); | |
| const nlohmann::json& json = res.jsonValue; | |
| EXPECT_EQ(json["Location"]["PartLocation"]["ServiceLabel"], "controller_a"); | |
| EXPECT_EQ(json["Location"]["PartLocationContext"], "SlotA"); | |
| EXPECT_EQ(json["Location"]["Oem"]["Google"]["Devpath"], | |
| "/phys/SlotA:device:controller_a"); | |
| EXPECT_EQ(json["Location"]["Oem"]["Google"]["EmbeddedLocationContext"], | |
| "controller:0"); | |
| } | |
| // Test path not fitting the regex | |
| { | |
| crow::Request req = CreateRequest( | |
| "/redfish/v1/Systems/system/Storage/StorageModuleA/Controllers/abc"); | |
| crow::Response res; | |
| res.result(boost::beast::http::status::ok); | |
| (*injector)->Inject(req, res); | |
| EXPECT_TRUE(res.jsonValue.empty()); | |
| } | |
| } | |
| } // namespace | |
| } // namespace devpath_plugin |