Report: make dbus properties writable

ReadingParametersFutureVersion interface for Reports is enhanced from
read-only to read write and supports modification of readingParameteres,
which is done by updating metrics at the first place and re-registering
them for sensors updates. Similar ReportActions interface is enhanced to
read write. Additionally reportActions policy was modified that reports
always contain 'LogToMetricReportsCollection' action. The whole change
enables Redfish support for PATCH method added on webserver side.

Tested:
- New unit tests were created, ran all new and previous UTs, all passed
- Tested under QEMU, interface was checked for RW via dbus cli commands,
  checked if Reading Parameters of the Report can be read or written,
  if metrics are properly updated and registration/unregistration for
  updates works properly, checked if actions of Report can be read or
  written, if actions are properly updated and related action behavior
  follows the change accordingly
- Tested under QEMU, verified if 'LogToMetricReportsCollection' action
  is always added when Report is created or when actions of Report are
  updated by dbus interface
- Tested via webserver if it communicates properly with dbus interfaces
  of Telemetry and read/write operations via Redfish can be successfully
  executed

Signed-off-by: Lukasz Kazmierczak <lukasz.kazmierczak@intel.com>
Change-Id: I7f2fe8eae1631c436cf61a516d5fd0b8358a76bd
diff --git a/src/interfaces/report_factory.hpp b/src/interfaces/report_factory.hpp
index 6a255a4..e1f9f2a 100644
--- a/src/interfaces/report_factory.hpp
+++ b/src/interfaces/report_factory.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "interfaces/json_storage.hpp"
+#include "interfaces/metric.hpp"
 #include "interfaces/report.hpp"
 #include "interfaces/report_manager.hpp"
 #include "types/report_action.hpp"
@@ -25,6 +26,13 @@
     virtual std::vector<LabeledMetricParameters>
         convertMetricParams(boost::asio::yield_context& yield,
                             const ReadingParameters& metricParams) const = 0;
+    virtual std::vector<LabeledMetricParameters>
+        convertMetricParams(const ReadingParameters& metricParams) const = 0;
+
+    virtual void
+        updateMetrics(std::vector<std::shared_ptr<interfaces::Metric>>& metrics,
+                      bool enabled,
+                      const ReadingParameters& metricParams) const = 0;
 
     virtual std::unique_ptr<interfaces::Report>
         make(const std::string& id, const std::string& name,
diff --git a/src/report.cpp b/src/report.cpp
index 18ca9c1..12d88c7 100644
--- a/src/report.cpp
+++ b/src/report.cpp
@@ -24,10 +24,11 @@
                interfaces::ReportManager& reportManager,
                interfaces::JsonStorage& reportStorageIn,
                std::vector<std::shared_ptr<interfaces::Metric>> metricsIn,
+               const interfaces::ReportFactory& reportFactory,
                const bool enabledIn, std::unique_ptr<interfaces::Clock> clock) :
     id(reportId),
     name(reportName), reportingType(reportingTypeIn), interval(intervalIn),
-    reportActions(std::move(reportActionsIn)),
+    reportActions(reportActionsIn.begin(), reportActionsIn.end()),
     sensorCount(getSensorCount(metricsIn)),
     appendLimit(deduceAppendLimit(appendLimitIn)),
     reportUpdates(reportUpdatesIn),
@@ -51,6 +52,8 @@
                 std::get<1>(sensorData.front()));
         });
 
+    reportActions.insert(ReportAction::logToMetricReportsCollection);
+
     deleteIface = objServer->add_unique_interface(
         getPath(), deleteIfaceName,
         [this, &ioc, &reportManager](auto& dbusIface) {
@@ -66,7 +69,7 @@
         });
 
     persistency = storeConfiguration();
-    reportIface = makeReportInterface();
+    reportIface = makeReportInterface(reportFactory);
 
     if (reportingType == ReportingType::periodic)
     {
@@ -153,22 +156,28 @@
     }
 }
 
+void Report::setReadingBuffer(const ReportUpdates newReportUpdates)
+{
+    if (reportingType != ReportingType::onRequest &&
+        (reportUpdates == ReportUpdates::overwrite ||
+         newReportUpdates == ReportUpdates::overwrite))
+    {
+        readingsBuffer.clearAndResize(
+            deduceBufferSize(newReportUpdates, reportingType));
+    }
+}
+
 void Report::setReportUpdates(const ReportUpdates newReportUpdates)
 {
     if (reportUpdates != newReportUpdates)
     {
-        if (reportingType != ReportingType::onRequest &&
-            (reportUpdates == ReportUpdates::overwrite ||
-             newReportUpdates == ReportUpdates::overwrite))
-        {
-            readingsBuffer.clearAndResize(
-                deduceBufferSize(newReportUpdates, reportingType));
-        }
+        setReadingBuffer(newReportUpdates);
         reportUpdates = newReportUpdates;
     }
 }
 
-std::unique_ptr<sdbusplus::asio::dbus_interface> Report::makeReportInterface()
+std::unique_ptr<sdbusplus::asio::dbus_interface>
+    Report::makeReportInterface(const interfaces::ReportFactory& reportFactory)
 {
     auto dbusIface =
         objServer->add_unique_interface(getPath(), reportIfaceName);
@@ -241,29 +250,69 @@
         },
         [this](const auto&) { return persistency; });
 
-    auto readingsFlag = sdbusplus::vtable::property_::none;
-    if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate))
-    {
-        readingsFlag = sdbusplus::vtable::property_::emits_change;
-    }
-    dbusIface->register_property_r("Readings", readings, readingsFlag,
+    dbusIface->register_property_r("Readings", readings,
+                                   sdbusplus::vtable::property_::emits_change,
                                    [this](const auto&) { return readings; });
-    dbusIface->register_property_r(
-        "ReportingType", std::string(), sdbusplus::vtable::property_::const_,
+    dbusIface->register_property_rw(
+        "ReportingType", std::string(),
+        sdbusplus::vtable::property_::emits_change,
+        [this](auto newVal, auto& oldVal) {
+            ReportingType tmp = utils::toReportingType(newVal);
+            if (tmp != reportingType)
+            {
+                if (tmp == ReportingType::onChange)
+                {
+                    throw sdbusplus::exception::SdBusError(
+                        static_cast<int>(std::errc::invalid_argument),
+                        "Invalid reportingType");
+                }
+                if (tmp == ReportingType::periodic)
+                {
+                    if (interval < ReportManager::minInterval)
+                    {
+                        throw sdbusplus::exception::SdBusError(
+                            static_cast<int>(std::errc::invalid_argument),
+                            "Invalid interval");
+                    }
+                    if (enabled == true)
+                    {
+                        scheduleTimer(interval);
+                    }
+                }
+                else
+                {
+                    timer.cancel();
+                }
+                reportingType = tmp;
+                setReadingBuffer(reportUpdates);
+                persistency = storeConfiguration();
+            }
+            oldVal = std::move(newVal);
+            return 1;
+        },
         [this](const auto&) { return utils::enumToString(reportingType); });
     dbusIface->register_property_r(
         "ReadingParameters", readingParametersPastVersion,
         sdbusplus::vtable::property_::const_,
         [this](const auto&) { return readingParametersPastVersion; });
-    dbusIface->register_property_r(
+    dbusIface->register_property_rw(
         "ReadingParametersFutureVersion", readingParameters,
-        sdbusplus::vtable::property_::const_,
+        sdbusplus::vtable::property_::emits_change,
+        [this, &reportFactory](auto newVal, auto& oldVal) {
+            reportFactory.updateMetrics(metrics, enabled, newVal);
+            readingParameters = toReadingParameters(
+                utils::transform(metrics, [](const auto& metric) {
+                    return metric->dumpConfiguration();
+                }));
+            persistency = storeConfiguration();
+            oldVal = std::move(newVal);
+            return 1;
+        },
         [this](const auto&) { return readingParameters; });
     dbusIface->register_property_r(
-        "EmitsReadingsUpdate", bool{}, sdbusplus::vtable::property_::const_,
+        "EmitsReadingsUpdate", bool{}, sdbusplus::vtable::property_::none,
         [this](const auto&) {
-            return utils::contains(reportActions,
-                                   ReportAction::emitsReadingsUpdate);
+            return reportActions.contains(ReportAction::emitsReadingsUpdate);
         });
     dbusIface->register_property_r("Name", std::string{},
                                    sdbusplus::vtable::property_::const_,
@@ -271,15 +320,32 @@
     dbusIface->register_property_r(
         "LogToMetricReportsCollection", bool{},
         sdbusplus::vtable::property_::const_, [this](const auto&) {
-            return utils::contains(reportActions,
-                                   ReportAction::logToMetricReportsCollection);
+            return reportActions.contains(
+                ReportAction::logToMetricReportsCollection);
         });
-    dbusIface->register_property_r(
+    dbusIface->register_property_rw(
         "ReportActions", std::vector<std::string>{},
-        sdbusplus::vtable::property_::const_, [this](const auto&) {
-            return utils::transform(reportActions, [](const auto reportAction) {
-                return utils::enumToString(reportAction);
-            });
+        sdbusplus::vtable::property_::emits_change,
+        [this](auto newVal, auto& oldVal) {
+            auto tmp = utils::transform<std::unordered_set>(
+                newVal, [](const auto& reportAction) {
+                    return utils::toReportAction(reportAction);
+                });
+            tmp.insert(ReportAction::logToMetricReportsCollection);
+
+            if (tmp != reportActions)
+            {
+                reportActions = tmp;
+                persistency = storeConfiguration();
+                oldVal = std::move(newVal);
+            }
+            return 1;
+        },
+        [this](const auto&) {
+            return utils::transform<std::vector>(
+                reportActions, [](const auto reportAction) {
+                    return utils::enumToString(reportAction);
+                });
         });
     dbusIface->register_property_r(
         "AppendLimit", appendLimit.value_or(sensorCount),
@@ -366,7 +432,10 @@
             .count(),
         std::vector<ReadingData>(readingsBuffer.begin(), readingsBuffer.end())};
 
-    reportIface->signal_property("Readings");
+    if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate))
+    {
+        reportIface->signal_property("Readings");
+    }
 }
 
 bool Report::storeConfiguration() const
diff --git a/src/report.hpp b/src/report.hpp
index 5ac6fd0..3b8b451 100644
--- a/src/report.hpp
+++ b/src/report.hpp
@@ -4,6 +4,7 @@
 #include "interfaces/json_storage.hpp"
 #include "interfaces/metric.hpp"
 #include "interfaces/report.hpp"
+#include "interfaces/report_factory.hpp"
 #include "interfaces/report_manager.hpp"
 #include "types/report_action.hpp"
 #include "types/report_types.hpp"
@@ -32,7 +33,8 @@
            interfaces::ReportManager& reportManager,
            interfaces::JsonStorage& reportStorage,
            std::vector<std::shared_ptr<interfaces::Metric>> metrics,
-           const bool enabled, std::unique_ptr<interfaces::Clock> clock);
+           const interfaces::ReportFactory& reportFactory, const bool enabled,
+           std::unique_ptr<interfaces::Clock> clock);
 
     Report(const Report&) = delete;
     Report(Report&&) = delete;
@@ -50,13 +52,15 @@
     }
 
   private:
-    std::unique_ptr<sdbusplus::asio::dbus_interface> makeReportInterface();
+    std::unique_ptr<sdbusplus::asio::dbus_interface>
+        makeReportInterface(const interfaces::ReportFactory& reportFactory);
     static void timerProc(boost::system::error_code, Report& self);
     void scheduleTimer(Milliseconds interval);
     std::optional<uint64_t>
         deduceAppendLimit(const uint64_t appendLimitIn) const;
     uint64_t deduceBufferSize(const ReportUpdates reportUpdatesIn,
                               const ReportingType reportingTypeIn) const;
+    void setReadingBuffer(const ReportUpdates newReportUpdates);
     void setReportUpdates(const ReportUpdates newReportUpdates);
     static uint64_t getSensorCount(
         std::vector<std::shared_ptr<interfaces::Metric>>& metrics);
@@ -70,7 +74,7 @@
     std::string name;
     ReportingType reportingType;
     Milliseconds interval;
-    std::vector<ReportAction> reportActions;
+    std::unordered_set<ReportAction> reportActions;
     ReadingParametersPastVersion readingParametersPastVersion;
     ReadingParameters readingParameters;
     bool persistency = false;
diff --git a/src/report_factory.cpp b/src/report_factory.cpp
index 261fc75..039846e 100644
--- a/src/report_factory.cpp
+++ b/src/report_factory.cpp
@@ -40,10 +40,60 @@
                 std::make_unique<Clock>());
         });
 
-    return std::make_unique<Report>(
-        bus->get_io_context(), objServer, id, name, reportingType,
-        reportActions, period, appendLimit, reportUpdates, reportManager,
-        reportStorage, std::move(metrics), enabled, std::make_unique<Clock>());
+    return std::make_unique<Report>(bus->get_io_context(), objServer, id, name,
+                                    reportingType, reportActions, period,
+                                    appendLimit, reportUpdates, reportManager,
+                                    reportStorage, std::move(metrics), *this,
+                                    enabled, std::make_unique<Clock>());
+}
+
+void ReportFactory::updateMetrics(
+    std::vector<std::shared_ptr<interfaces::Metric>>& metrics, bool enabled,
+    const ReadingParameters& metricParams) const
+{
+    auto labeledMetricParams = convertMetricParams(metricParams);
+    std::vector<std::shared_ptr<interfaces::Metric>> oldMetrics = metrics;
+    std::vector<std::shared_ptr<interfaces::Metric>> newMetrics;
+
+    for (const auto& labeledMetricParam : labeledMetricParams)
+    {
+        auto existing = std::find_if(oldMetrics.begin(), oldMetrics.end(),
+                                     [labeledMetricParam](auto metric) {
+                                         return labeledMetricParam ==
+                                                metric->dumpConfiguration();
+                                     });
+
+        if (existing != oldMetrics.end())
+        {
+            newMetrics.emplace_back(*existing);
+            oldMetrics.erase(existing);
+            continue;
+        }
+
+        namespace ts = utils::tstring;
+        newMetrics.emplace_back(std::make_shared<Metric>(
+            getSensors(labeledMetricParam.at_label<ts::SensorPath>()),
+            labeledMetricParam.at_label<ts::OperationType>(),
+            labeledMetricParam.at_label<ts::Id>(),
+            labeledMetricParam.at_label<ts::CollectionTimeScope>(),
+            labeledMetricParam.at_label<ts::CollectionDuration>(),
+            std::make_unique<Clock>()));
+
+        if (enabled)
+        {
+            newMetrics.back()->initialize();
+        }
+    }
+
+    if (enabled)
+    {
+        for (auto& metric : oldMetrics)
+        {
+            metric->deinitialize();
+        }
+    }
+
+    metrics = std::move(newMetrics);
 }
 
 Sensors ReportFactory::getSensors(
@@ -65,8 +115,30 @@
     boost::asio::yield_context& yield,
     const ReadingParameters& metricParams) const
 {
+    if (metricParams.empty())
+    {
+        return {};
+    }
     auto tree = utils::getSubTreeSensors(yield, bus);
+    return getMetricParamsFromSensorTree(metricParams, tree);
+}
 
+std::vector<LabeledMetricParameters> ReportFactory::convertMetricParams(
+    const ReadingParameters& metricParams) const
+{
+    if (metricParams.empty())
+    {
+        return {};
+    }
+    auto tree = utils::getSubTreeSensors(bus);
+    return getMetricParamsFromSensorTree(metricParams, tree);
+}
+
+std::vector<LabeledMetricParameters>
+    ReportFactory::getMetricParamsFromSensorTree(
+        const ReadingParameters& metricParams,
+        const std::vector<utils::SensorTree>& tree) const
+{
     return utils::transform(metricParams, [&tree](const auto& item) {
         auto [sensorPaths, operationType, id, collectionTimeScope,
               collectionDuration] = item;
diff --git a/src/report_factory.hpp b/src/report_factory.hpp
index 14f9f39..8431f40 100644
--- a/src/report_factory.hpp
+++ b/src/report_factory.hpp
@@ -4,6 +4,7 @@
 #include "interfaces/sensor.hpp"
 #include "sensor_cache.hpp"
 #include "types/sensor_types.hpp"
+#include "utils/dbus_mapper.hpp"
 
 #include <boost/asio/io_context.hpp>
 #include <sdbusplus/asio/object_server.hpp>
@@ -20,6 +21,14 @@
         boost::asio::yield_context& yield,
         const ReadingParameters& metricParams) const override;
 
+    std::vector<LabeledMetricParameters> convertMetricParams(
+        const ReadingParameters& metricParams) const override;
+
+    void
+        updateMetrics(std::vector<std::shared_ptr<interfaces::Metric>>& metrics,
+                      bool enabled,
+                      const ReadingParameters& metricParams) const override;
+
     std::unique_ptr<interfaces::Report>
         make(const std::string& reportId, const std::string& name,
              const ReportingType reportingType,
@@ -33,6 +42,9 @@
 
   private:
     Sensors getSensors(const std::vector<LabeledSensorInfo>& sensorPaths) const;
+    std::vector<LabeledMetricParameters> getMetricParamsFromSensorTree(
+        const ReadingParameters& metricParams,
+        const std::vector<utils::SensorTree>& tree) const;
 
     std::shared_ptr<sdbusplus::asio::connection> bus;
     std::shared_ptr<sdbusplus::asio::object_server> objServer;
diff --git a/src/report_manager.cpp b/src/report_manager.cpp
index 31363c4..10035a1 100644
--- a/src/report_manager.cpp
+++ b/src/report_manager.cpp
@@ -56,7 +56,7 @@
                 "SupportedOperationTypes", std::vector<std::string>{},
                 sdbusplus::vtable::property_::const_,
                 [](const auto&) -> std::vector<std::string> {
-                    return utils::transform<std::vector<std::string>>(
+                    return utils::transform<std::vector>(
                         utils::convDataOperationType, [](const auto& item) {
                             return std::string(item.first);
                         });
diff --git a/src/utils/contains.hpp b/src/utils/contains.hpp
index ea1dd16..0a26cc1 100644
--- a/src/utils/contains.hpp
+++ b/src/utils/contains.hpp
@@ -13,6 +13,12 @@
     container.find(container.begin()->first);
 };
 
+template <class T>
+concept HasMemberContains = requires(T container)
+{
+    container.contains(*container.begin());
+};
+
 } // namespace detail
 
 template <detail::HasMemberFind T>
@@ -22,6 +28,12 @@
     return container.find(key) != container.end();
 }
 
+template <detail::HasMemberContains T>
+inline bool contains(const T& container, const typename T::value_type& key)
+{
+    return container.contains(key);
+}
+
 template <class T>
 inline bool contains(const T& container, const typename T::value_type& key)
 {
diff --git a/src/utils/transform.hpp b/src/utils/transform.hpp
index 4a28950..fc52fc3 100644
--- a/src/utils/transform.hpp
+++ b/src/utils/transform.hpp
@@ -16,27 +16,29 @@
 
 } // namespace detail
 
-template <class R, class Container, class F>
-auto transform(const Container& container, F&& functor)
+template <template <class, class...> class R, class Container, class Functor>
+inline auto transform(const Container& container, Functor&& f)
 {
-    auto result = R{};
+    auto result = R<decltype(f(*container.begin()))>{};
 
     if constexpr (detail::has_member_reserve<decltype(result)>)
     {
         result.reserve(container.size());
     }
+
     std::transform(container.begin(), container.end(),
                    std::inserter(result, result.end()),
-                   std::forward<F>(functor));
+                   std::forward<Functor>(f));
+
     return result;
 }
 
-template <template <class, class...> class Container, class T, class... Args,
-          class F>
-auto transform(const Container<T, Args...>& container, F&& functor)
+template <template <class, class...> class Container, class Functor,
+          class... Args>
+inline auto transform(const Container<Args...>& container, Functor&& f)
 {
-    using R = Container<decltype(functor(*container.begin()))>;
-    return transform<R>(container, std::forward<F>(functor));
+    return transform<Container, Container<Args...>, Functor>(
+        container, std::forward<Functor>(f));
 }
 
 } // namespace utils
diff --git a/tests/src/mocks/report_factory_mock.hpp b/tests/src/mocks/report_factory_mock.hpp
index 6a7d8bd..d0fc14c 100644
--- a/tests/src/mocks/report_factory_mock.hpp
+++ b/tests/src/mocks/report_factory_mock.hpp
@@ -32,6 +32,10 @@
     {
         using namespace testing;
 
+        ON_CALL(*this, convertMetricParams(_))
+            .WillByDefault(
+                WithArgs<0>(Invoke(&ReportFactoryMock::convertToLabeled)));
+
         ON_CALL(*this, convertMetricParams(_, _))
             .WillByDefault(
                 WithArgs<1>(Invoke(&ReportFactoryMock::convertToLabeled)));
@@ -47,6 +51,14 @@
                 (boost::asio::yield_context&, const ReadingParameters&),
                 (const, override));
 
+    MOCK_METHOD(std::vector<LabeledMetricParameters>, convertMetricParams,
+                (const ReadingParameters&), (const, override));
+
+    MOCK_METHOD(void, updateMetrics,
+                (std::vector<std::shared_ptr<interfaces::Metric>> & metrics,
+                 bool enabled, const ReadingParameters&),
+                (const, override));
+
     MOCK_METHOD(std::unique_ptr<interfaces::Report>, make,
                 (const std::string&, const std::string&, const ReportingType,
                  const std::vector<ReportAction>&, Milliseconds, uint64_t,
diff --git a/tests/src/params/report_params.hpp b/tests/src/params/report_params.hpp
index 56f619a..295be36 100644
--- a/tests/src/params/report_params.hpp
+++ b/tests/src/params/report_params.hpp
@@ -112,7 +112,8 @@
     std::string reportIdProperty = "TestId";
     std::string reportNameProperty = "TestReport";
     ReportingType reportingTypeProperty = ReportingType::onRequest;
-    std::vector<ReportAction> reportActionsProperty;
+    std::vector<ReportAction> reportActionsProperty = {
+        ReportAction::logToMetricReportsCollection};
     Milliseconds intervalProperty = ReportManager::minInterval;
     uint64_t appendLimitProperty = 123;
     ReportUpdates reportUpdatesProperty = ReportUpdates::overwrite;
diff --git a/tests/src/test_report.cpp b/tests/src/test_report.cpp
index 765f8e4..9afa421 100644
--- a/tests/src/test_report.cpp
+++ b/tests/src/test_report.cpp
@@ -6,6 +6,7 @@
 #include "messages/update_report_ind.hpp"
 #include "mocks/json_storage_mock.hpp"
 #include "mocks/metric_mock.hpp"
+#include "mocks/report_factory_mock.hpp"
 #include "mocks/report_manager_mock.hpp"
 #include "params/report_params.hpp"
 #include "report.hpp"
@@ -33,7 +34,9 @@
 
     std::unique_ptr<ReportManagerMock> reportManagerMock =
         std::make_unique<NiceMock<ReportManagerMock>>();
-    testing::NiceMock<StorageMock> storageMock;
+    std::unique_ptr<ReportFactoryMock> reportFactoryMock =
+        std::make_unique<NiceMock<ReportFactoryMock>>();
+    NiceMock<StorageMock> storageMock;
     std::vector<std::shared_ptr<MetricMock>> metricMocks;
     std::unique_ptr<ClockFake> clockFakePtr = std::make_unique<ClockFake>();
     ClockFake& clockFake = *clockFakePtr;
@@ -69,6 +72,24 @@
         }
     }
 
+    std::vector<std::shared_ptr<interfaces::Metric>>
+        getMetricsFromReadingParams(const ReadingParameters& params)
+    {
+        const auto metricParameters =
+            reportFactoryMock->convertMetricParams(params);
+        std::vector<std::shared_ptr<MetricMock>> metricMocks;
+
+        for (size_t i = 0; i < metricParameters.size(); ++i)
+        {
+            metricMocks.emplace_back(std::make_shared<NiceMock<MetricMock>>());
+            ON_CALL(*metricMocks[i], dumpConfiguration())
+                .WillByDefault(Return(metricParameters[i]));
+        }
+
+        return utils::convContainer<std::shared_ptr<interfaces::Metric>>(
+            metricMocks);
+    }
+
     void SetUp() override
     {
         sut = makeReport(ReportParams());
@@ -91,7 +112,7 @@
             params.reportUpdates(), *reportManagerMock, storageMock,
             utils::convContainer<std::shared_ptr<interfaces::Metric>>(
                 metricMocks),
-            params.enabled(), std::move(clockFakePtr));
+            *reportFactoryMock, params.enabled(), std::move(clockFakePtr));
     }
 
     template <class T>
@@ -180,6 +201,147 @@
                 Eq(Readings{}));
 }
 
+TEST_F(TestReport, setReadingParametersWithNewParams)
+{
+    ReadingParameters newParams = toReadingParameters(
+        std::vector<LabeledMetricParameters>{{LabeledMetricParameters{
+            {LabeledSensorInfo{"Service",
+                               "/xyz/openbmc_project/sensors/power/psu",
+                               "NewMetadata123"}},
+            OperationType::avg,
+            "NewMetricId123",
+            CollectionTimeScope::startup,
+            CollectionDuration(250ms)}}});
+    auto metrics = getMetricsFromReadingParams(newParams);
+
+    EXPECT_CALL(*reportFactoryMock, updateMetrics(_, _, _))
+        .WillOnce(SetArgReferee<0>(metrics));
+    EXPECT_THAT(
+        setProperty(sut->getPath(), "ReadingParametersFutureVersion", newParams)
+            .value(),
+        Eq(boost::system::errc::success));
+    EXPECT_THAT(getProperty<ReadingParameters>(
+                    sut->getPath(), "ReadingParametersFutureVersion"),
+                Eq(newParams));
+}
+
+TEST_F(TestReport, setReportingTypeWithValidNewType)
+{
+    std::string newType = "Periodic";
+    std::string currType = utils::enumToString(defaultParams.reportingType());
+
+    EXPECT_THAT(newType, Ne(currType));
+    EXPECT_THAT(setProperty(sut->getPath(), "ReportingType", newType).value(),
+                Eq(boost::system::errc::success));
+    EXPECT_THAT(getProperty<std::string>(sut->getPath(), "ReportingType"),
+                Eq(newType));
+}
+
+TEST_F(TestReport, setReportingTypeWithInvalidType)
+{
+    std::string newType = "Periodic_ABC";
+    std::string prevType = utils::enumToString(defaultParams.reportingType());
+
+    EXPECT_THAT(setProperty(sut->getPath(), "ReportingType", newType).value(),
+                Eq(boost::system::errc::invalid_argument));
+    EXPECT_THAT(getProperty<std::string>(sut->getPath(), "ReportingType"),
+                Eq(prevType));
+}
+
+TEST_F(TestReport, setReportActionsWithValidNewActions)
+{
+    std::vector<std::string> newActions = {"EmitsReadingsUpdate"};
+    std::vector<std::string> currActions =
+        utils::transform(defaultParams.reportActions(),
+                         [](const auto v) { return utils::enumToString(v); });
+
+    EXPECT_THAT(newActions, Ne(currActions));
+    EXPECT_THAT(
+        setProperty(sut->getPath(), "ReportActions", newActions).value(),
+        Eq(boost::system::errc::success));
+    EXPECT_THAT(
+        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
+        UnorderedElementsAre("EmitsReadingsUpdate",
+                             "LogToMetricReportsCollection"));
+}
+
+TEST_F(TestReport, setReportActionsWithValidUnsortedActions)
+{
+    std::vector<std::string> newActions = {"LogToMetricReportsCollection",
+                                           "EmitsReadingsUpdate"};
+    std::vector<std::string> expectedActions = {"EmitsReadingsUpdate",
+                                                "LogToMetricReportsCollection"};
+    std::vector<std::string> currActions =
+        utils::transform(defaultParams.reportActions(),
+                         [](const auto v) { return utils::enumToString(v); });
+
+    EXPECT_THAT(newActions, Ne(currActions));
+    EXPECT_THAT(
+        setProperty(sut->getPath(), "ReportActions", newActions).value(),
+        Eq(boost::system::errc::success));
+    EXPECT_THAT(
+        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
+        Eq(expectedActions));
+}
+
+TEST_F(TestReport, setReportActionsWithEmptyActions)
+{
+    std::vector<std::string> newActions = {};
+    std::vector<std::string> expectedActions = {"LogToMetricReportsCollection"};
+    std::vector<std::string> currActions =
+        utils::transform(defaultParams.reportActions(),
+                         [](const auto v) { return utils::enumToString(v); });
+
+    EXPECT_THAT(newActions, Ne(currActions));
+    EXPECT_THAT(
+        setProperty(sut->getPath(), "ReportActions", newActions).value(),
+        Eq(boost::system::errc::success));
+    EXPECT_THAT(
+        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
+        Eq(expectedActions));
+}
+
+TEST_F(TestReport, setReportActionsWithInvalidActions)
+{
+    std::vector<std::string> invalidActions = {"EmitsReadingsUpdate_1"};
+    EXPECT_THAT(
+        setProperty(sut->getPath(), "ReportActions", invalidActions).value(),
+        Eq(boost::system::errc::invalid_argument));
+    EXPECT_THAT(
+        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
+        Eq(utils::transform(defaultParams.reportActions(), [](const auto v) {
+            return utils::enumToString(v);
+        })));
+}
+
+TEST_F(TestReport, createReportWithEmptyActions)
+{
+    std::vector<std::string> expectedActions = {"LogToMetricReportsCollection"};
+
+    sut = makeReport(ReportParams().reportId("TestId_1").reportActions({}));
+    EXPECT_THAT(
+        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
+        Eq(expectedActions));
+}
+
+TEST_F(TestReport, createReportWithValidUnsortedActions)
+{
+    std::vector<std::string> newActions = {"LogToMetricReportsCollection",
+                                           "EmitsReadingsUpdate"};
+    std::vector<std::string> expectedActions = {"EmitsReadingsUpdate",
+                                                "LogToMetricReportsCollection"};
+
+    sut = makeReport(
+        ReportParams()
+            .reportId("TestId_1")
+            .reportActions(utils::transform(newActions, [](const auto& action) {
+                return utils::toReportAction(action);
+            })));
+    EXPECT_THAT(
+        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
+        Eq(expectedActions));
+}
+
 TEST_F(TestReport, setEnabledWithNewValue)
 {
     bool newValue = !defaultParams.enabled();
diff --git a/tests/src/test_transform.cpp b/tests/src/test_transform.cpp
index 3f7c5f9..d47d8d3 100644
--- a/tests/src/test_transform.cpp
+++ b/tests/src/test_transform.cpp
@@ -31,9 +31,8 @@
 TEST(TestTransform, transformsArrayToVector)
 {
     std::array<int, 3> input = {1, 2, 3};
-    std::vector<std::string> output =
-        utils::transform<std::vector<std::string>>(
-            input, [](int v) { return std::to_string(v); });
+    std::vector<std::string> output = utils::transform<std::vector>(
+        input, [](int v) { return std::to_string(v); });
     EXPECT_FALSE(utils::detail::has_member_reserve<decltype(input)>);
     EXPECT_TRUE(utils::detail::has_member_reserve<decltype(output)>);
     ASSERT_THAT(output, ElementsAre("1", "2", "3"));
diff --git a/tests/src/utils/conv_container.hpp b/tests/src/utils/conv_container.hpp
index 3806a2a..fb9b123 100644
--- a/tests/src/utils/conv_container.hpp
+++ b/tests/src/utils/conv_container.hpp
@@ -1,19 +1,14 @@
 #pragma once
 
-#include <algorithm>
+#include "utils/transform.hpp"
 
 namespace utils
 {
 
-template <class R, class T, class... Args,
-          template <class, class...> class Container>
-auto convContainer(const Container<T, Args...>& container)
+template <class R, class Container>
+auto convContainer(const Container& container)
 {
-    Container<R> result;
-    std::transform(container.begin(), container.end(),
-                   std::back_inserter(result),
-                   [](const auto& item) -> R { return item; });
-    return result;
+    return transform(container, [](const auto& item) -> R { return item; });
 }
 
 } // namespace utils