| #include "control.hpp" | |
| #include <cstdint> | |
| #include <memory> | |
| #include <string> | |
| #include <vector> | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include "absl/functional/any_invocable.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "dbus_utility.hpp" | |
| #include "test/redfish-core/lib/snapshot_fixture.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "managed_store.hpp" | |
| #include "managed_store_types.hpp" | |
| #include "test/g3/mock_managed_store.hpp" | |
| #include "test/g3/mock_managed_store_test.hpp" | |
| #include "sdbusplus/message/native_types.hpp" | |
| namespace redfish { | |
| namespace { | |
| using ::dbus::utility::DbusVariantType; | |
| using ::managedStore::KeyType; | |
| using ::managedStore::ManagedType; | |
| using ::managedStore::SimulateFailedAsyncPostDbusCallThreadSafeAction; | |
| using ::managedStore::SimulateSuccessfulAsyncPostDbusCallThreadSafeAction; | |
| using ::testing::_; | |
| using ::testing::An; | |
| constexpr absl::string_view kChassisId = "platform1"; | |
| constexpr absl::string_view kControlId = "PL1"; | |
| constexpr absl::string_view kControlId2 = "PL2"; | |
| constexpr absl::string_view kServiceName = "xyz.openbmc_project.ControlService"; | |
| constexpr absl::string_view kInterfaceName = | |
| "xyz.openbmc_project.Chassis.Control"; | |
| class ControlTest : public SnapshotFixture { | |
| protected: | |
| void TearDown() override { | |
| auto store = dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()); | |
| if (store != nullptr) { | |
| for (const auto& key : registered_keys_) { | |
| EXPECT_TRUE(store->evictMockObjectFromManagedStore(key).ok()); | |
| } | |
| } | |
| } | |
| void UpsertMockObject(const KeyType& key, | |
| const std::shared_ptr<managedStore::ValueType>& value) { | |
| auto store = dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()); | |
| ASSERT_NE(store, nullptr); | |
| ASSERT_TRUE(store->upsertMockObjectIntoManagedStore(key, value).ok()); | |
| registered_keys_.push_back(key); | |
| } | |
| std::string GetControlPath(absl::string_view control_id) const { | |
| return absl::StrCat("/xyz/openbmc_project/control/", kChassisId, "/", | |
| control_id); | |
| } | |
| void MockControlPaths(const std::vector<std::string>& control_ids) { | |
| std::vector<std::string> interfaces = {std::string(kInterfaceName)}; | |
| KeyType key(ManagedType::kManagedSubtreePaths, | |
| absl::StrCat("/xyz/openbmc_project/control/", kChassisId), 0, | |
| interfaces); | |
| dbus::utility::MapperGetSubTreePathsResponse mockPaths; | |
| for (const auto& cid : control_ids) { | |
| mockPaths.push_back(GetControlPath(cid)); | |
| } | |
| std::shared_ptr<managedStore::ValueType> value = | |
| managedStore::MockManagedStoreTest::CreateValueType(mockPaths); | |
| UpsertMockObject(key, value); | |
| } | |
| void MockMapperObject(absl::string_view control_id) { | |
| std::vector<std::string> interfaces = {std::string(kInterfaceName)}; | |
| KeyType mapperKey(ManagedType::kManagedMapperObject, | |
| GetControlPath(control_id), interfaces); | |
| dbus::utility::MapperGetObject mapperObject = { | |
| {std::string(kServiceName), {std::string(kInterfaceName)}}}; | |
| std::shared_ptr<managedStore::ValueType> mapperValue = | |
| managedStore::MockManagedStoreTest::CreateValueType(mapperObject); | |
| UpsertMockObject(mapperKey, mapperValue); | |
| } | |
| void MockMapperObjectEmpty(absl::string_view control_id) { | |
| std::vector<std::string> interfaces = {std::string(kInterfaceName)}; | |
| KeyType mapperKey(ManagedType::kManagedMapperObject, | |
| GetControlPath(control_id), interfaces); | |
| dbus::utility::MapperGetObject mapperObject = {}; | |
| std::shared_ptr<managedStore::ValueType> mapperValue = | |
| managedStore::MockManagedStoreTest::CreateValueType(mapperObject); | |
| UpsertMockObject(mapperKey, mapperValue); | |
| } | |
| void MockProperties(absl::string_view control_id, | |
| const dbus::utility::DBusPropertiesMap& props) { | |
| KeyType propsKey( | |
| ManagedType::kManagedPropertyMap, kServiceName, | |
| sdbusplus::message::object_path(GetControlPath(control_id)), | |
| kInterfaceName); | |
| std::shared_ptr<managedStore::ValueType> propsValue = | |
| managedStore::MockManagedStoreTest::CreateValueType(props); | |
| UpsertMockObject(propsKey, propsValue); | |
| } | |
| void MockPropertiesError(absl::string_view control_id) { | |
| KeyType propsKey( | |
| ManagedType::kManagedPropertyMap, kServiceName, | |
| sdbusplus::message::object_path(GetControlPath(control_id)), | |
| kInterfaceName); | |
| dbus::utility::DBusPropertiesMap emptyProps = {}; | |
| std::shared_ptr<managedStore::ValueType> propsErrorValue = | |
| managedStore::MockManagedStoreTest::CreateErrorValueType( | |
| emptyProps, | |
| boost::system::errc::make_error_code( | |
| boost::system::errc::io_error)); | |
| UpsertMockObject(propsKey, propsErrorValue); | |
| } | |
| void MockMapperObjectError(absl::string_view control_id) { | |
| std::vector<std::string> interfaces = {std::string(kInterfaceName)}; | |
| KeyType mapperKey(ManagedType::kManagedMapperObject, | |
| GetControlPath(control_id), interfaces); | |
| dbus::utility::MapperGetObject emptyObject = {}; | |
| std::shared_ptr<managedStore::ValueType> mapperErrorValue = | |
| managedStore::MockManagedStoreTest::CreateErrorValueType( | |
| emptyObject, | |
| boost::system::errc::make_error_code( | |
| boost::system::errc::io_error)); | |
| UpsertMockObject(mapperKey, mapperErrorValue); | |
| } | |
| std::vector<KeyType> registered_keys_; | |
| }; | |
| TEST_F(ControlTest, GetControlCollectionTestReturnsCorrectResponse) { | |
| MockControlPaths({std::string(kControlId), std::string(kControlId2)}); | |
| getControlCollection(app_, CreateRequest(), share_async_resp_, | |
| std::string(kChassisId)); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(json, nlohmann::json::parse(R"json( | |
| { | |
| "@odata.id": "/redfish/v1/Chassis/platform1/Controls", | |
| "@odata.type": "#ControlCollection.v1_0_0.ControlCollection", | |
| "Members": [ | |
| { | |
| "@odata.id": "/redfish/v1/Chassis/platform1/Controls/PL1" | |
| }, | |
| { | |
| "@odata.id": "/redfish/v1/Chassis/platform1/Controls/PL2" | |
| } | |
| ], | |
| "Members@odata.count": 2, | |
| "Name": "Control Collection" | |
| })json")); | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| } | |
| TEST_F(ControlTest, GetControlConfigurationReturnsCorrectResponse) { | |
| MockMapperObject(kControlId); | |
| MockProperties(kControlId, {{"SetPoint", uint32_t{240}}, | |
| {"SetPointUnits", std::string("W")}, | |
| {"ControlType", std::string("Power")}, | |
| {"PhysicalContext", std::string("Processor")}}); | |
| getControlConfiguration(app_, CreateRequest(), share_async_resp_, | |
| std::string(kChassisId), std::string(kControlId)); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(json, nlohmann::json::parse(R"json( | |
| { | |
| "@odata.id": "/redfish/v1/Chassis/platform1/Controls/PL1", | |
| "@odata.type": "#Control.v1_5_1.Control", | |
| "ControlMode": "Manual", | |
| "ControlType": "Power", | |
| "Id": "PL1", | |
| "Name": "Chassis Control PL1", | |
| "PhysicalContext": "Processor", | |
| "SetPoint": 240, | |
| "SetPointUnits": "W" | |
| })json")); | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| } | |
| TEST_F(ControlTest, GetControlConfigurationDisabledControl) { | |
| MockMapperObject(kControlId2); | |
| MockProperties(kControlId2, {{"SetPoint", uint32_t{0}}, | |
| {"SetPointUnits", std::string("W")}}); | |
| getControlConfiguration(app_, CreateRequest(), share_async_resp_, | |
| std::string(kChassisId), std::string(kControlId2)); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(json, nlohmann::json::parse(R"json( | |
| { | |
| "@odata.id": "/redfish/v1/Chassis/platform1/Controls/PL2", | |
| "@odata.type": "#Control.v1_5_1.Control", | |
| "ControlMode": "Disabled", | |
| "Id": "PL2", | |
| "Name": "Chassis Control PL2" | |
| })json")); | |
| EXPECT_FALSE(json.contains("SetPointUnits")); | |
| EXPECT_FALSE(json.contains("SetPoint")); | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| } | |
| TEST_F(ControlTest, GetControlConfigurationNotFound) { | |
| MockMapperObjectEmpty(kControlId); | |
| getControlConfiguration(app_, CreateRequest(), share_async_resp_, | |
| std::string(kChassisId), std::string(kControlId)); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::not_found); | |
| } | |
| TEST_F(ControlTest, GetControlConfigurationDBusError) { | |
| MockMapperObject(kControlId); | |
| MockPropertiesError(kControlId); | |
| getControlConfiguration(app_, CreateRequest(), share_async_resp_, | |
| std::string(kChassisId), std::string(kControlId)); | |
| RunIoUntilDone(); | |
| // Verify that internalError was invoked and returned 500 | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::internal_server_error); | |
| } | |
| TEST_F(ControlTest, PatchControlConfigurationSuccess) { | |
| MockMapperObject(kControlId); | |
| EXPECT_CALL( | |
| *dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()), | |
| PostDbusCallToIoContextThreadSafe( | |
| _, An<absl::AnyInvocable<void(const boost::system::error_code&)>&&>(), | |
| std::string(kServiceName), GetControlPath(kControlId), | |
| "org.freedesktop.DBus.Properties", "Set", std::string(kInterfaceName), | |
| "SetPoint", DbusVariantType(uint32_t{250}))) | |
| .Times(1) | |
| .WillOnce(SimulateSuccessfulAsyncPostDbusCallThreadSafeAction:: | |
| SimulateSuccessfulAsyncPostDbusCall()); | |
| patchControlConfiguration(app_, CreateRequest(R"({"SetPoint": 250})"), | |
| share_async_resp_, std::string(kChassisId), | |
| std::string(kControlId)); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| } | |
| TEST_F(ControlTest, PatchControlConfigurationSetPropertyError) { | |
| MockMapperObject(kControlId); | |
| EXPECT_CALL( | |
| *dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()), | |
| PostDbusCallToIoContextThreadSafe( | |
| _, An<absl::AnyInvocable<void(const boost::system::error_code&)>&&>(), | |
| std::string(kServiceName), GetControlPath(kControlId), | |
| "org.freedesktop.DBus.Properties", "Set", std::string(kInterfaceName), | |
| "SetPoint", DbusVariantType(uint32_t{250}))) | |
| .Times(1) | |
| .WillOnce(SimulateFailedAsyncPostDbusCallThreadSafeAction:: | |
| SimulateFailedAsyncPostDbusCall()); | |
| patchControlConfiguration(app_, CreateRequest(R"({"SetPoint": 250})"), | |
| share_async_resp_, std::string(kChassisId), | |
| std::string(kControlId)); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::internal_server_error); | |
| } | |
| TEST_F(ControlTest, PatchControlConfigurationMissingSetPoint) { | |
| patchControlConfiguration(app_, CreateRequest(R"({})"), | |
| share_async_resp_, std::string(kChassisId), | |
| std::string(kControlId)); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::bad_request); | |
| } | |
| TEST_F(ControlTest, PatchControlConfigurationNullSetPoint) { | |
| patchControlConfiguration(app_, CreateRequest(R"({"SetPoint": null})"), | |
| share_async_resp_, std::string(kChassisId), | |
| std::string(kControlId)); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::bad_request); | |
| } | |
| TEST_F(ControlTest, PatchControlConfigurationUnknownProperty) { | |
| patchControlConfiguration(app_, CreateRequest(R"({"OtherProperty": 250})"), | |
| share_async_resp_, std::string(kChassisId), | |
| std::string(kControlId)); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::bad_request); | |
| } | |
| TEST_F(ControlTest, PatchControlConfigurationInvalidSetPoint) { | |
| patchControlConfiguration( | |
| app_, CreateRequest(R"({"SetPoint": "invalid_string"})"), | |
| share_async_resp_, std::string(kChassisId), std::string(kControlId)); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::bad_request); | |
| } | |
| TEST_F(ControlTest, PatchControlConfigurationNotFound) { | |
| MockMapperObjectEmpty(kControlId); | |
| patchControlConfiguration(app_, CreateRequest(R"({"SetPoint": 250})"), | |
| share_async_resp_, std::string(kChassisId), | |
| std::string(kControlId)); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::not_found); | |
| } | |
| TEST_F(ControlTest, PatchControlConfigurationDBusMapperError) { | |
| MockMapperObjectError(kControlId); | |
| patchControlConfiguration(app_, CreateRequest(R"({"SetPoint": 250})"), | |
| share_async_resp_, std::string(kChassisId), | |
| std::string(kControlId)); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::internal_server_error); | |
| } | |
| } // namespace | |
| } // namespace redfish |