| #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 |