Changed dbus add report interface

- metric parameters now take single sensor instead of list
- added interface support for new operation types

Tested:
- All telemetry tests are passing.

Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
Change-Id: Id3a41c48e81a287e7d205ae1c747daa36d4cdb29
diff --git a/src/interfaces/metric.hpp b/src/interfaces/metric.hpp
index 5a960a4..deb3ed6 100644
--- a/src/interfaces/metric.hpp
+++ b/src/interfaces/metric.hpp
@@ -16,7 +16,7 @@
     virtual ~Metric() = default;
 
     virtual void initialize() = 0;
-    virtual const std::vector<MetricValue>& getReadings() const = 0;
+    virtual const MetricValue& getReading() const = 0;
     virtual LabeledMetricParameters dumpConfiguration() const = 0;
 };
 
diff --git a/src/interfaces/types.hpp b/src/interfaces/types.hpp
index 3cc069e..a5ed0db 100644
--- a/src/interfaces/types.hpp
+++ b/src/interfaces/types.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "operation_type.hpp"
 #include "utils/labeled_tuple.hpp"
 #include "utils/tstring.hpp"
 
@@ -11,17 +12,17 @@
 #include <vector>
 
 using ReadingParameters =
-    std::vector<std::tuple<std::vector<sdbusplus::message::object_path>,
-                           std::string, std::string, std::string>>;
+    std::vector<std::tuple<sdbusplus::message::object_path, std::string,
+                           std::string, std::string>>;
 
 using LabeledSensorParameters =
     utils::LabeledTuple<std::tuple<std::string, std::string>,
                         utils::tstring::Service, utils::tstring::Path>;
 
 using LabeledMetricParameters =
-    utils::LabeledTuple<std::tuple<std::vector<LabeledSensorParameters>,
-                                   std::string, std::string, std::string>,
-                        utils::tstring::SensorPaths,
+    utils::LabeledTuple<std::tuple<LabeledSensorParameters, OperationType,
+                                   std::string, std::string>,
+                        utils::tstring::SensorPath,
                         utils::tstring::OperationType, utils::tstring::Id,
                         utils::tstring::MetricMetadata>;
 
diff --git a/src/metric.cpp b/src/metric.cpp
index b12f3a6..2a73536 100644
--- a/src/metric.cpp
+++ b/src/metric.cpp
@@ -5,58 +5,52 @@
 
 #include <algorithm>
 
-Metric::Metric(std::vector<std::shared_ptr<interfaces::Sensor>> sensors,
-               std::string operationType, std::string id,
+Metric::Metric(std::shared_ptr<interfaces::Sensor> sensor,
+               OperationType operationType, std::string id,
                std::string metadata) :
-    sensors(std::move(sensors)),
-    operationType(std::move(operationType)), id(std::move(id)),
-    metadata(std::move(metadata))
+    sensor(std::move(sensor)),
+    operationType(std::move(operationType)), reading{std::move(id),
+                                                     std::move(metadata), 0.,
+                                                     0u}
 {}
 
 void Metric::initialize()
 {
-    readings = std::vector<MetricValue>(sensors.size(),
-                                        MetricValue{id, metadata, 0., 0u});
-
-    for (auto& sensor : sensors)
-    {
-        sensor->registerForUpdates(weak_from_this());
-    }
+    sensor->registerForUpdates(weak_from_this());
 }
 
-const std::vector<MetricValue>& Metric::getReadings() const
+const MetricValue& Metric::getReading() const
 {
-    return readings;
+    return reading;
 }
 
-void Metric::sensorUpdated(interfaces::Sensor& sensor, uint64_t timestamp)
+void Metric::sensorUpdated(interfaces::Sensor& notifier, uint64_t timestamp)
 {
-    MetricValue& mv = findMetric(sensor);
+    MetricValue& mv = findMetric(notifier);
     mv.timestamp = timestamp;
 }
 
-void Metric::sensorUpdated(interfaces::Sensor& sensor, uint64_t timestamp,
+void Metric::sensorUpdated(interfaces::Sensor& notifier, uint64_t timestamp,
                            double value)
 {
-    MetricValue& mv = findMetric(sensor);
+    MetricValue& mv = findMetric(notifier);
     mv.timestamp = timestamp;
     mv.value = value;
 }
 
-MetricValue& Metric::findMetric(interfaces::Sensor& sensor)
+MetricValue& Metric::findMetric(interfaces::Sensor& notifier)
 {
-    auto it =
-        std::find_if(sensors.begin(), sensors.end(),
-                     [&sensor](const auto& s) { return s.get() == &sensor; });
-    auto index = std::distance(sensors.begin(), it);
-    return readings.at(index);
+    if (sensor.get() != &notifier)
+    {
+        throw std::out_of_range("unknown sensor");
+    }
+    return reading;
 }
 
 LabeledMetricParameters Metric::dumpConfiguration() const
 {
-    auto sensorPaths = utils::transform(sensors, [](const auto& sensor) {
-        return LabeledSensorParameters(sensor->id().service, sensor->id().path);
-    });
-    return LabeledMetricParameters(std::move(sensorPaths), operationType, id,
-                                   metadata);
+    auto sensorPath =
+        LabeledSensorParameters(sensor->id().service, sensor->id().path);
+    return LabeledMetricParameters(std::move(sensorPath), operationType,
+                                   reading.id, reading.metadata);
 }
diff --git a/src/metric.hpp b/src/metric.hpp
index 39f525c..aab4d15 100644
--- a/src/metric.hpp
+++ b/src/metric.hpp
@@ -10,11 +10,11 @@
     public std::enable_shared_from_this<Metric>
 {
   public:
-    Metric(std::vector<std::shared_ptr<interfaces::Sensor>> sensors,
-           std::string operationType, std::string id, std::string metadata);
+    Metric(std::shared_ptr<interfaces::Sensor> sensor,
+           OperationType operationType, std::string id, std::string metadata);
 
     void initialize() override;
-    const std::vector<MetricValue>& getReadings() const override;
+    const MetricValue& getReading() const override;
     void sensorUpdated(interfaces::Sensor&, uint64_t) override;
     void sensorUpdated(interfaces::Sensor&, uint64_t, double value) override;
     LabeledMetricParameters dumpConfiguration() const override;
@@ -22,9 +22,7 @@
   private:
     MetricValue& findMetric(interfaces::Sensor&);
 
-    std::vector<std::shared_ptr<interfaces::Sensor>> sensors;
-    std::string operationType;
-    std::string id;
-    std::string metadata;
-    std::vector<MetricValue> readings;
+    std::shared_ptr<interfaces::Sensor> sensor;
+    OperationType operationType;
+    MetricValue reading;
 };
diff --git a/src/operation_type.hpp b/src/operation_type.hpp
new file mode 100644
index 0000000..a4f085c
--- /dev/null
+++ b/src/operation_type.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "utils/conversion.hpp"
+
+#include <array>
+#include <cstdint>
+#include <string_view>
+
+enum class OperationType : uint32_t
+{
+    single,
+    max,
+    min,
+    avg,
+    sum
+};
+
+namespace utils
+{
+
+constexpr std::array<std::pair<std::string_view, OperationType>, 5>
+    convDataOperationType = {
+        {std::make_pair<std::string_view, OperationType>("SINGLE",
+                                                         OperationType::single),
+         std::make_pair<std::string_view, OperationType>("MAX",
+                                                         OperationType::max),
+         std::make_pair<std::string_view, OperationType>("MIN",
+                                                         OperationType::min),
+         std::make_pair<std::string_view, OperationType>("AVG",
+                                                         OperationType::avg),
+         std::make_pair<std::string_view, OperationType>("SUM",
+                                                         OperationType::sum)}};
+
+inline OperationType
+    toOperationType(std::underlying_type_t<OperationType> value)
+{
+    return toEnum<OperationType, OperationType::single, OperationType::sum>(
+        value);
+}
+
+inline OperationType stringToOperationType(const std::string& value)
+{
+    return stringToEnum(convDataOperationType, value);
+}
+
+inline std::string enumToString(OperationType value)
+{
+    return std::string(enumToString(convDataOperationType, value));
+}
+
+} // namespace utils
diff --git a/src/report.cpp b/src/report.cpp
index 59028f4..77189c1 100644
--- a/src/report.cpp
+++ b/src/report.cpp
@@ -143,23 +143,14 @@
 
 void Report::updateReadings()
 {
-    auto numElements = std::accumulate(
-        metrics.begin(), metrics.end(), 0u, [](auto sum, const auto& metric) {
-            return sum + metric->getReadings().size();
-        });
+    std::tuple_element_t<1, Readings> readingsCache(metrics.size());
 
-    std::tuple_element_t<1, Readings> readingsCache(numElements);
-
-    auto it = readingsCache.begin();
-
-    for (const auto& metric : metrics)
-    {
-        for (const auto& reading : metric->getReadings())
-        {
-            *(it++) = std::make_tuple(reading.id, reading.metadata,
-                                      reading.value, reading.timestamp);
-        }
-    }
+    std::transform(std::begin(metrics), std::end(metrics),
+                   std::begin(readingsCache), [](const auto& metric) {
+                       const auto& reading = metric->getReading();
+                       return std::make_tuple(reading.id, reading.metadata,
+                                              reading.value, reading.timestamp);
+                   });
 
     std::get<0>(readings) = std::time(0);
     std::get<1>(readings) = std::move(readingsCache);
diff --git a/src/report.hpp b/src/report.hpp
index 2f470d3..97afe03 100644
--- a/src/report.hpp
+++ b/src/report.hpp
@@ -75,5 +75,5 @@
         "/xyz/openbmc_project/Telemetry/Reports/";
     static constexpr const char* deleteIfaceName =
         "xyz.openbmc_project.Object.Delete";
-    static constexpr size_t reportVersion = 2;
+    static constexpr size_t reportVersion = 3;
 };
diff --git a/src/report_factory.cpp b/src/report_factory.cpp
index 2cd5da3..9dfdbb0 100644
--- a/src/report_factory.cpp
+++ b/src/report_factory.cpp
@@ -3,6 +3,7 @@
 #include "metric.hpp"
 #include "report.hpp"
 #include "sensor.hpp"
+#include "utils/conversion.hpp"
 #include "utils/dbus_mapper.hpp"
 #include "utils/transform.hpp"
 
@@ -41,7 +42,7 @@
         [this](const LabeledMetricParameters& param)
             -> std::shared_ptr<interfaces::Metric> {
             return std::make_shared<Metric>(
-                getSensors(param.at_index<0>()), param.at_index<1>(),
+                getSensor(param.at_index<0>()), param.at_index<1>(),
                 param.at_index<2>(), param.at_index<3>());
         });
 
@@ -51,19 +52,14 @@
         reportManager, reportStorage, std::move(metrics));
 }
 
-std::vector<std::shared_ptr<interfaces::Sensor>> ReportFactory::getSensors(
-    const std::vector<LabeledSensorParameters>& sensorPaths) const
+std::shared_ptr<interfaces::Sensor>
+    ReportFactory::getSensor(const LabeledSensorParameters& sensorPath) const
 {
-    return utils::transform(sensorPaths,
-                            [this](const LabeledSensorParameters& param)
-                                -> std::shared_ptr<interfaces::Sensor> {
-                                using namespace utils::tstring;
+    using namespace utils::tstring;
 
-                                return sensorCache.makeSensor<Sensor>(
-                                    param.at_label<Service>(),
-                                    param.at_label<Path>(),
-                                    bus->get_io_context(), bus);
-                            });
+    return sensorCache.makeSensor<Sensor>(sensorPath.at_label<Service>(),
+                                          sensorPath.at_label<Path>(),
+                                          bus->get_io_context(), bus);
 }
 
 std::vector<LabeledMetricParameters> ReportFactory::convertMetricParams(
@@ -73,24 +69,22 @@
     auto tree = utils::getSubTreeSensors(yield, bus);
 
     return utils::transform(metricParams, [&tree](const auto& item) {
-        std::vector<LabeledSensorParameters> sensors;
+        const auto& [sensorPath, operationType, id, metadata] = item;
 
-        for (const auto& sensorPath : std::get<0>(item))
+        auto it = std::find_if(
+            tree.begin(), tree.end(),
+            [&sensorPath](const auto& v) { return v.first == sensorPath; });
+
+        if (it != tree.end() && it->second.size() == 1)
         {
-            auto it = std::find_if(
-                tree.begin(), tree.end(),
-                [&sensorPath](const auto& v) { return v.first == sensorPath; });
-
-            if (it != tree.end())
-            {
-                for (const auto& [service, ifaces] : it->second)
-                {
-                    sensors.emplace_back(service, sensorPath);
-                }
-            }
+            const auto& [service, ifaces] = it->second.front();
+            return LabeledMetricParameters(
+                LabeledSensorParameters(service, sensorPath),
+                utils::stringToOperationType(operationType), id, metadata);
         }
 
-        return LabeledMetricParameters(std::move(sensors), std::get<1>(item),
-                                       std::get<2>(item), std::get<3>(item));
+        throw sdbusplus::exception::SdBusError(
+            static_cast<int>(std::errc::invalid_argument),
+            "Could not find service for provided sensors");
     });
 }
diff --git a/src/report_factory.hpp b/src/report_factory.hpp
index 6d90902..550eb81 100644
--- a/src/report_factory.hpp
+++ b/src/report_factory.hpp
@@ -34,8 +34,8 @@
             const override;
 
   private:
-    std::vector<std::shared_ptr<interfaces::Sensor>> getSensors(
-        const std::vector<LabeledSensorParameters>& sensorPaths) const;
+    std::shared_ptr<interfaces::Sensor>
+        getSensor(const LabeledSensorParameters& sensorPath) const;
     std::vector<LabeledMetricParameters>
         convertMetricParams(boost::asio::yield_context& yield,
                             const ReadingParameters& metricParams) const;
diff --git a/src/report_manager.cpp b/src/report_manager.cpp
index 1d3e47b..9ea6026 100644
--- a/src/report_manager.cpp
+++ b/src/report_manager.cpp
@@ -2,6 +2,7 @@
 
 #include "interfaces/types.hpp"
 #include "report.hpp"
+#include "utils/conversion.hpp"
 #include "utils/transform.hpp"
 
 #include <phosphor-logging/log.hpp>
@@ -92,22 +93,26 @@
             static_cast<int>(std::errc::invalid_argument), "Invalid interval");
     }
 
-    for (const auto& param : readingParams)
-    {
-        const auto& sensors = std::get<0>(param);
-        if (sensors.size() != 1)
-        {
-            throw sdbusplus::exception::SdBusError(
-                static_cast<int>(std::errc::not_supported),
-                "Only single sensor per metric is allowed");
-        }
-    }
     if (readingParams.size() > maxReadingParams)
+
     {
         throw sdbusplus::exception::SdBusError(
             static_cast<int>(std::errc::argument_list_too_long),
             "Too many reading parameters");
     }
+
+    try
+    {
+        for (const auto& item : readingParams)
+        {
+            utils::stringToOperationType(std::get<1>(item));
+        }
+    }
+    catch (const std::exception& e)
+    {
+        throw sdbusplus::exception::SdBusError(
+            static_cast<int>(std::errc::invalid_argument), e.what());
+    }
 }
 
 interfaces::Report& ReportManager::addReport(
@@ -136,12 +141,10 @@
             using namespace utils::tstring;
 
             return ReadingParameters::value_type(
-                utils::transform(param.at_index<0>(),
-                                 [](const LabeledSensorParameters& p) {
-                                     return sdbusplus::message::object_path(
-                                         p.at_label<Path>());
-                                 }),
-                param.at_index<1>(), param.at_index<2>(), param.at_index<3>());
+                sdbusplus::message::object_path(
+                    param.at_index<0>().at_label<Path>()),
+                utils::enumToString(param.at_index<1>()), param.at_index<2>(),
+                param.at_index<3>());
         });
 
     verifyAddReport(reportName, reportingType, interval, metricParams);
diff --git a/src/utils/conversion.hpp b/src/utils/conversion.hpp
index 7db4be8..5e8ef81 100644
--- a/src/utils/conversion.hpp
+++ b/src/utils/conversion.hpp
@@ -1,18 +1,57 @@
 #pragma once
 
+#include <algorithm>
+#include <array>
 #include <stdexcept>
+#include <string>
 
 namespace utils
 {
 
 template <class T, T first, T last>
-inline T toEnum(int x)
+inline T toEnum(std::underlying_type_t<T> x)
 {
-    if (x < static_cast<decltype(x)>(first) ||
-        x > static_cast<decltype(x)>(last))
+    if (x < static_cast<std::underlying_type_t<T>>(first) ||
+        x > static_cast<std::underlying_type_t<T>>(last))
     {
         throw std::out_of_range("Value is not in range of enum");
     }
     return static_cast<T>(x);
 }
+
+template <class T>
+inline std::underlying_type_t<T> toUnderlying(T value)
+{
+    return static_cast<std::underlying_type_t<T>>(value);
+}
+
+template <class T, size_t N>
+inline T stringToEnum(const std::array<std::pair<std::string_view, T>, N>& data,
+                      const std::string& value)
+{
+    auto it = std::find_if(
+        std::begin(data), std::end(data),
+        [&value](const auto& item) { return item.first == value; });
+    if (it == std::end(data))
+    {
+        throw std::out_of_range("Value is not in range of enum");
+    }
+    return it->second;
+}
+
+template <class T, size_t N>
+inline std::string_view
+    enumToString(const std::array<std::pair<std::string_view, T>, N>& data,
+                 T value)
+{
+    auto it = std::find_if(
+        std::begin(data), std::end(data),
+        [value](const auto& item) { return item.second == value; });
+    if (it == std::end(data))
+    {
+        throw std::out_of_range("Value is not in range of enum");
+    }
+    return it->first;
+}
+
 } // namespace utils
diff --git a/src/utils/tstring.hpp b/src/utils/tstring.hpp
index f0a401f..a8d3e90 100644
--- a/src/utils/tstring.hpp
+++ b/src/utils/tstring.hpp
@@ -16,11 +16,11 @@
     }
 };
 
-struct SensorPaths
+struct SensorPath
 {
     static std::string str()
     {
-        return "sensorPaths";
+        return "sensorPath";
     }
 };
 
diff --git a/tests/src/helpers/interfaces/labeled_reading_parameter_helpers.hpp b/tests/src/helpers/interfaces/labeled_reading_parameter_helpers.hpp
index 3666a62..9a46d0c 100644
--- a/tests/src/helpers/interfaces/labeled_reading_parameter_helpers.hpp
+++ b/tests/src/helpers/interfaces/labeled_reading_parameter_helpers.hpp
@@ -15,8 +15,8 @@
     using testing::PrintToString;
 
     (*os) << "{ ";
-    (*os) << utils::tstring::SensorPaths::str() << ": "
-          << PrintToString(o.at_label<utils::tstring::SensorPaths>()) << ", ";
+    (*os) << utils::tstring::SensorPath::str() << ": "
+          << PrintToString(o.at_label<utils::tstring::SensorPath>()) << ", ";
     (*os) << utils::tstring::OperationType::str() << ": "
           << PrintToString(o.at_label<utils::tstring::OperationType>()) << ", ";
     (*os) << utils::tstring::Id::str() << ": "
@@ -26,4 +26,4 @@
     (*os) << " }";
 }
 
-} // namespace utils
\ No newline at end of file
+} // namespace utils
diff --git a/tests/src/mocks/metric_mock.hpp b/tests/src/mocks/metric_mock.hpp
index 98bad87..2f31f1e 100644
--- a/tests/src/mocks/metric_mock.hpp
+++ b/tests/src/mocks/metric_mock.hpp
@@ -11,13 +11,12 @@
     {
         using namespace testing;
 
-        ON_CALL(*this, getReadings())
-            .WillByDefault(ReturnRefOfCopy(std::vector<MetricValue>()));
+        ON_CALL(*this, getReading())
+            .WillByDefault(ReturnRefOfCopy(MetricValue()));
     }
 
     MOCK_METHOD(void, initialize, (), (override));
-    MOCK_METHOD(const std::vector<MetricValue>&, getReadings, (),
-                (const, override));
+    MOCK_METHOD(const MetricValue&, getReading, (), (const, override));
     MOCK_METHOD(LabeledMetricParameters, dumpConfiguration, (),
                 (const, override));
 };
diff --git a/tests/src/params/report_params.hpp b/tests/src/params/report_params.hpp
index fa1340c..25422bc 100644
--- a/tests/src/params/report_params.hpp
+++ b/tests/src/params/report_params.hpp
@@ -84,12 +84,12 @@
     ReadingParameters readingParametersProperty = {
         {{sdbusplus::message::object_path(
              "/xyz/openbmc_project/sensors/power/p1")},
-         "SINGLE",
+         utils::enumToString(OperationType::single),
          "MetricId1",
          "Metadata1"},
         {{sdbusplus::message::object_path(
              "/xyz/openbmc_project/sensors/power/p2")},
-         "SINGLE",
+         utils::enumToString(OperationType::single),
          "MetricId2",
          "Metadata2"}};
 };
diff --git a/tests/src/params/trigger_params.hpp b/tests/src/params/trigger_params.hpp
index 464c68b..9aed64d 100644
--- a/tests/src/params/trigger_params.hpp
+++ b/tests/src/params/trigger_params.hpp
@@ -2,6 +2,8 @@
 
 #include "interfaces/trigger_types.hpp"
 
+#include <sdbusplus/message.hpp>
+
 #include <chrono>
 #include <utility>
 
diff --git a/tests/src/test_conversion.cpp b/tests/src/test_conversion.cpp
index e3984ca..f24592f 100644
--- a/tests/src/test_conversion.cpp
+++ b/tests/src/test_conversion.cpp
@@ -4,7 +4,7 @@
 
 using namespace testing;
 
-class TestEnum : public Test
+class TestConversion : public Test
 {
   public:
     enum class Enum
@@ -18,16 +18,63 @@
     {
         return utils::toEnum<Enum, Enum::zero, Enum::two>(x);
     }
+
+    Enum stringToEnum(const std::string& value)
+    {
+        return utils::stringToEnum(convDataEnum, value);
+    }
+
+    std::string enumToString(Enum value)
+    {
+        return std::string(utils::enumToString(convDataEnum, value));
+    }
+
+    static constexpr std::array<std::pair<std::string_view, Enum>, 3>
+        convDataEnum = {
+            {std::make_pair<std::string_view, Enum>("zero", Enum::zero),
+             std::make_pair<std::string_view, Enum>("one", Enum::one),
+             std::make_pair<std::string_view, Enum>("two", Enum::two)}};
 };
 
-TEST_F(TestEnum, passValueInRangeExpectToGetValidOutput)
+TEST_F(TestConversion, passValueInRangeExpectToGetValidOutput)
 {
     EXPECT_EQ(toEnum(0), Enum::zero);
     EXPECT_EQ(toEnum(2), Enum::two);
 }
 
-TEST_F(TestEnum, passInvalidValueExpectToThrowOutOfRangeException)
+TEST_F(TestConversion, passInvalidValueExpectToThrowOutOfRangeException)
 {
     EXPECT_THROW(toEnum(-1), std::out_of_range);
     EXPECT_THROW(toEnum(3), std::out_of_range);
 }
+
+TEST_F(TestConversion, convertsToUnderlyingType)
+{
+    EXPECT_THAT(utils::toUnderlying(Enum::one), Eq(1));
+    EXPECT_THAT(utils::toUnderlying(Enum::two), Eq(2));
+    EXPECT_THAT(utils::toUnderlying(Enum::zero), Eq(0));
+}
+
+TEST_F(TestConversion, convertsEnumToString)
+{
+    EXPECT_THAT(enumToString(Enum::one), Eq("one"));
+    EXPECT_THAT(enumToString(Enum::two), Eq("two"));
+    EXPECT_THAT(enumToString(Enum::zero), Eq("zero"));
+}
+
+TEST_F(TestConversion, convertsStringToEnum)
+{
+    EXPECT_THAT(stringToEnum("one"), Eq(Enum::one));
+    EXPECT_THAT(stringToEnum("two"), Eq(Enum::two));
+    EXPECT_THAT(stringToEnum("zero"), Eq(Enum::zero));
+}
+
+TEST_F(TestConversion, enumToStringThrowsWhenUknownEnumPassed)
+{
+    EXPECT_THROW(enumToString(static_cast<Enum>(77)), std::out_of_range);
+}
+
+TEST_F(TestConversion, stringToEnumThrowsWhenUknownStringPassed)
+{
+    EXPECT_THROW(stringToEnum("four"), std::out_of_range);
+}
diff --git a/tests/src/test_metric.cpp b/tests/src/test_metric.cpp
index 22fc97b..e91750d 100644
--- a/tests/src/test_metric.cpp
+++ b/tests/src/test_metric.cpp
@@ -2,130 +2,111 @@
 #include "metric.hpp"
 #include "mocks/sensor_mock.hpp"
 #include "utils/conv_container.hpp"
+#include "utils/conversion.hpp"
+#include "utils/tstring.hpp"
 
 #include <gmock/gmock.h>
 
 using namespace testing;
+namespace tstring = utils::tstring;
 
 using Timestamp = uint64_t;
 
 class TestMetric : public Test
 {
   public:
-    std::vector<std::shared_ptr<SensorMock>> sensorMocks = {
-        std::make_shared<NiceMock<SensorMock>>(),
-        std::make_shared<NiceMock<SensorMock>>(),
-        std::make_shared<NiceMock<SensorMock>>()};
+    std::shared_ptr<SensorMock> sensorMock =
+        std::make_shared<NiceMock<SensorMock>>();
 
     std::shared_ptr<Metric> sut = std::make_shared<Metric>(
-        utils::convContainer<std::shared_ptr<interfaces::Sensor>>(sensorMocks),
-        "op", "id", "metadata");
+        sensorMock, OperationType::avg, "id", "metadata");
 };
 
 TEST_F(TestMetric, subscribesForSensorDuringInitialization)
 {
-    for (auto& sensor : sensorMocks)
+    EXPECT_CALL(*sensorMock,
+                registerForUpdates(Truly([sut = sut.get()](const auto& a0) {
+                    return a0.lock().get() == sut;
+                })));
+
+    sut->initialize();
+}
+
+TEST_F(TestMetric, containsEmptyReadingAfterCreated)
+{
+    ASSERT_THAT(sut->getReading(), MetricValue({"id", "metadata", 0., 0u}));
+}
+
+class TestMetricAfterInitialization : public TestMetric
+{
+  public:
+    TestMetricAfterInitialization()
     {
-        EXPECT_CALL(*sensor,
-                    registerForUpdates(Truly([sut = sut.get()](const auto& a0) {
-                        return a0.lock().get() == sut;
-                    })));
+        sut->initialize();
     }
+};
 
-    sut->initialize();
+TEST_F(TestMetricAfterInitialization, containsEmptyReading)
+{
+    ASSERT_THAT(sut->getReading(), MetricValue({"id", "metadata", 0., 0u}));
 }
 
-TEST_F(TestMetric, containsNoReadingsWhenNotInitialized)
+TEST_F(TestMetricAfterInitialization, updatesMetricValuesOnSensorUpdate)
 {
-    ASSERT_THAT(sut->getReadings(), ElementsAre());
+    sut->sensorUpdated(*sensorMock, Timestamp{18}, 31.2);
+
+    ASSERT_THAT(sut->getReading(),
+                Eq(MetricValue{"id", "metadata", 31.2, 18u}));
 }
 
-TEST_F(TestMetric, containsEmptyReadingsAfterInitialize)
+TEST_F(TestMetricAfterInitialization,
+       throwsWhenUpdateIsPerformedOnUnknownSensor)
 {
-    sut->initialize();
-
-    ASSERT_THAT(sut->getReadings(),
-                ElementsAre(MetricValue{"id", "metadata", 0., 0u},
-                            MetricValue{"id", "metadata", 0., 0u},
-                            MetricValue{"id", "metadata", 0., 0u}));
-}
-
-TEST_F(TestMetric, throwsWhenUpdateIsPerformedWhenNotInitialized)
-{
-    EXPECT_THROW(sut->sensorUpdated(*sensorMocks[0], Timestamp{10}),
-                 std::out_of_range);
-    EXPECT_THROW(sut->sensorUpdated(*sensorMocks[1], Timestamp{10}, 20.0),
-                 std::out_of_range);
-}
-
-TEST_F(TestMetric, updatesMetricValuesOnSensorUpdate)
-{
-    sut->initialize();
-
-    sut->sensorUpdated(*sensorMocks[2], Timestamp{18}, 31.0);
-    sut->sensorUpdated(*sensorMocks[0], Timestamp{21});
-
-    ASSERT_THAT(sut->getReadings(),
-                ElementsAre(MetricValue{"id", "metadata", 0., 21u},
-                            MetricValue{"id", "metadata", 0., 0u},
-                            MetricValue{"id", "metadata", 31., 18u}));
-}
-
-TEST_F(TestMetric, throwsWhenUpdateIsPerformedOnUnknownSensor)
-{
-    sut->initialize();
-
     auto sensor = std::make_shared<StrictMock<SensorMock>>();
     EXPECT_THROW(sut->sensorUpdated(*sensor, Timestamp{10}), std::out_of_range);
     EXPECT_THROW(sut->sensorUpdated(*sensor, Timestamp{10}, 20.0),
                  std::out_of_range);
 }
 
-TEST_F(TestMetric, containsIdInConfigurationDump)
+TEST_F(TestMetricAfterInitialization, containsIdInConfigurationDump)
 {
     const auto conf = sut->dumpConfiguration();
 
     EXPECT_THAT(conf.at_label<utils::tstring::Id>(), Eq("id"));
-    EXPECT_THAT(conf.to_json().at("id"), Eq(nlohmann::json("id")));
+    EXPECT_THAT(conf.to_json().at(tstring::Id::str()),
+                Eq(nlohmann::json("id")));
 }
 
-TEST_F(TestMetric, containsOpInJsonDump)
+TEST_F(TestMetricAfterInitialization, containsOpInJsonDump)
 {
     const auto conf = sut->dumpConfiguration();
 
-    EXPECT_THAT(conf.at_label<utils::tstring::OperationType>(), Eq("op"));
-    EXPECT_THAT(conf.to_json().at("operationType"), Eq(nlohmann::json("op")));
+    EXPECT_THAT(conf.at_label<utils::tstring::OperationType>(),
+                Eq(OperationType::avg));
+    EXPECT_THAT(conf.to_json().at(tstring::OperationType::str()),
+                Eq(nlohmann::json(utils::toUnderlying(OperationType::avg))));
 }
 
-TEST_F(TestMetric, containsMetadataInJsonDump)
+TEST_F(TestMetricAfterInitialization, containsMetadataInJsonDump)
 {
     const auto conf = sut->dumpConfiguration();
 
     EXPECT_THAT(conf.at_label<utils::tstring::MetricMetadata>(),
                 Eq("metadata"));
-    EXPECT_THAT(conf.to_json().at("metricMetadata"),
+    EXPECT_THAT(conf.to_json().at(tstring::MetricMetadata::str()),
                 Eq(nlohmann::json("metadata")));
 }
 
-TEST_F(TestMetric, containsSensorPathsInJsonDump)
+TEST_F(TestMetricAfterInitialization, containsSensorPathInJsonDump)
 {
-    for (size_t i = 0; i < sensorMocks.size(); ++i)
-    {
-        const auto no = std::to_string(i);
-        ON_CALL(*sensorMocks[i], id())
-            .WillByDefault(
-                Return(SensorMock::makeId("service" + no, "path" + no)));
-    }
+    ON_CALL(*sensorMock, id())
+        .WillByDefault(Return(SensorMock::makeId("service1", "path1")));
 
     const auto conf = sut->dumpConfiguration();
 
-    EXPECT_THAT(conf.at_label<utils::tstring::SensorPaths>(),
-                ElementsAre(LabeledSensorParameters("service0", "path0"),
-                            LabeledSensorParameters("service1", "path1"),
-                            LabeledSensorParameters("service2", "path2")));
+    EXPECT_THAT(conf.at_label<utils::tstring::SensorPath>(),
+                Eq(LabeledSensorParameters("service1", "path1")));
     EXPECT_THAT(
-        conf.to_json().at("sensorPaths"),
-        Eq(nlohmann::json({{{"service", "service0"}, {"path", "path0"}},
-                           {{"service", "service1"}, {"path", "path1"}},
-                           {{"service", "service2"}, {"path", "path2"}}})));
+        conf.to_json().at(tstring::SensorPath::str()),
+        Eq(nlohmann::json({{"service", "service1"}, {"path", "path1"}})));
 }
diff --git a/tests/src/test_report.cpp b/tests/src/test_report.cpp
index f6c4ee7..5378f48 100644
--- a/tests/src/test_report.cpp
+++ b/tests/src/test_report.cpp
@@ -8,12 +8,14 @@
 #include "report_manager.hpp"
 #include "utils/conv_container.hpp"
 #include "utils/set_exception.hpp"
+#include "utils/tstring.hpp"
 
 #include <sdbusplus/exception.hpp>
 
 using namespace testing;
 using namespace std::literals::string_literals;
 using namespace std::chrono_literals;
+namespace tstring = utils::tstring;
 
 class TestReport : public Test
 {
@@ -33,13 +35,13 @@
 
     TestReport()
     {
-        ON_CALL(*metricMocks[0], getReadings())
-            .WillByDefault(ReturnRefOfCopy(std::vector<MetricValue>(
-                {MetricValue{"a", "b", 17.1, 114},
-                 MetricValue{"aaa", "bbb", 21.7, 100}})));
-        ON_CALL(*metricMocks[1], getReadings())
-            .WillByDefault(ReturnRefOfCopy(
-                std::vector<MetricValue>({MetricValue{"aa", "bb", 42.0, 74}})));
+        ON_CALL(*metricMocks[0], getReading())
+            .WillByDefault(ReturnRefOfCopy(MetricValue{"a", "b", 17.1, 114}));
+        ON_CALL(*metricMocks[1], getReading())
+            .WillByDefault(ReturnRefOfCopy(MetricValue{"aa", "bb", 42.0, 74}));
+        ON_CALL(*metricMocks[2], getReading())
+            .WillByDefault(
+                ReturnRefOfCopy(MetricValue{"aaa", "bbb", 100.7, 21}));
 
         for (size_t i = 0; i < metricMocks.size(); ++i)
         {
@@ -47,11 +49,11 @@
 
             auto id = std::to_string(i);
 
-            auto sensorParameters = std::vector<LabeledSensorParameters>(
-                {LabeledSensorParameters("service"s + id, "path"s + id)});
-            auto metricParameters =
-                LabeledMetricParameters(std::move(sensorParameters), "op"s + id,
-                                        "id"s + id, "metadata"s + id);
+            auto sensorParameters =
+                LabeledSensorParameters("service"s + id, "path"s + id);
+            auto metricParameters = LabeledMetricParameters(
+                std::move(sensorParameters), utils::toOperationType(i),
+                "id"s + id, "metadata"s + id);
 
             ON_CALL(*metricMocks[i], dumpConfiguration())
                 .WillByDefault(Return(std::move(metricParameters)));
@@ -241,35 +243,35 @@
 
 INSTANTIATE_TEST_SUITE_P(
     _, TestReportStore,
-    Values(
-        std::make_pair("Version"s, nlohmann::json(2)),
-        std::make_pair("Name"s, nlohmann::json(ReportParams().reportName())),
-        std::make_pair("ReportingType",
-                       nlohmann::json(ReportParams().reportingType())),
-        std::make_pair("EmitsReadingsUpdate",
-                       nlohmann::json(ReportParams().emitReadingUpdate())),
-        std::make_pair(
-            "LogToMetricReportsCollection",
-            nlohmann::json(ReportParams().logToMetricReportCollection())),
-        std::make_pair("Interval",
-                       nlohmann::json(ReportParams().interval().count())),
-        std::make_pair(
-            "ReadingParameters",
-            nlohmann::json({{{"sensorPaths",
-                              {{{"service", "service0"}, {"path", "path0"}}}},
-                             {"operationType", "op0"},
-                             {"id", "id0"},
-                             {"metricMetadata", "metadata0"}},
-                            {{"sensorPaths",
-                              {{{"service", "service1"}, {"path", "path1"}}}},
-                             {"operationType", "op1"},
-                             {"id", "id1"},
-                             {"metricMetadata", "metadata1"}},
-                            {{"sensorPaths",
-                              {{{"service", "service2"}, {"path", "path2"}}}},
-                             {"operationType", "op2"},
-                             {"id", "id2"},
-                             {"metricMetadata", "metadata2"}}}))));
+    Values(std::make_pair("Version"s, nlohmann::json(3)),
+           std::make_pair("Name"s, nlohmann::json(ReportParams().reportName())),
+           std::make_pair("ReportingType",
+                          nlohmann::json(ReportParams().reportingType())),
+           std::make_pair("EmitsReadingsUpdate",
+                          nlohmann::json(ReportParams().emitReadingUpdate())),
+           std::make_pair(
+               "LogToMetricReportsCollection",
+               nlohmann::json(ReportParams().logToMetricReportCollection())),
+           std::make_pair("Interval",
+                          nlohmann::json(ReportParams().interval().count())),
+           std::make_pair(
+               "ReadingParameters",
+               nlohmann::json(
+                   {{{tstring::SensorPath::str(),
+                      {{"service", "service0"}, {"path", "path0"}}},
+                     {tstring::OperationType::str(), OperationType::single},
+                     {tstring::Id::str(), "id0"},
+                     {tstring::MetricMetadata::str(), "metadata0"}},
+                    {{tstring::SensorPath::str(),
+                      {{"service", "service1"}, {"path", "path1"}}},
+                     {tstring::OperationType::str(), OperationType::max},
+                     {tstring::Id::str(), "id1"},
+                     {tstring::MetricMetadata::str(), "metadata1"}},
+                    {{tstring::SensorPath::str(),
+                      {{"service", "service2"}, {"path", "path2"}}},
+                     {tstring::OperationType::str(), OperationType::min},
+                     {tstring::Id::str(), "id2"},
+                     {tstring::MetricMetadata::str(), "metadata2"}}}))));
 
 TEST_P(TestReportStore, settingPersistencyToTrueStoresReport)
 {
@@ -416,8 +418,8 @@
 
     EXPECT_THAT(readings,
                 ElementsAre(std::make_tuple("a"s, "b"s, 17.1, 114u),
-                            std::make_tuple("aaa"s, "bbb"s, 21.7, 100u),
-                            std::make_tuple("aa"s, "bb"s, 42.0, 74u)));
+                            std::make_tuple("aa"s, "bb"s, 42.0, 74u),
+                            std::make_tuple("aaa"s, "bbb"s, 100.7, 21u)));
 }
 
 class TestReportNonOnRequestType :
@@ -493,8 +495,8 @@
 
     EXPECT_THAT(readings,
                 ElementsAre(std::make_tuple("a"s, "b"s, 17.1, 114u),
-                            std::make_tuple("aaa"s, "bbb"s, 21.7, 100u),
-                            std::make_tuple("aa"s, "bb"s, 42.0, 74u)));
+                            std::make_tuple("aa"s, "bb"s, 42.0, 74u),
+                            std::make_tuple("aaa"s, "bbb"s, 100.7, 21u)));
 }
 
 class TestReportInitialization : public TestReport
diff --git a/tests/src/test_report_manager.cpp b/tests/src/test_report_manager.cpp
index 30628d6..65e48f4 100644
--- a/tests/src/test_report_manager.cpp
+++ b/tests/src/test_report_manager.cpp
@@ -5,9 +5,11 @@
 #include "params/report_params.hpp"
 #include "report.hpp"
 #include "report_manager.hpp"
+#include "utils/conversion.hpp"
 #include "utils/transform.hpp"
 
 using namespace testing;
+using namespace std::string_literals;
 using namespace std::chrono_literals;
 
 class TestReportManager : public Test
@@ -246,6 +248,37 @@
     sut->updateReport("NotAReport");
 }
 
+class TestReportManagerWithAggregationOperationType :
+    public TestReportManager,
+    public WithParamInterface<OperationType>
+{
+  public:
+    OperationType operationType = GetParam();
+};
+
+INSTANTIATE_TEST_SUITE_P(_, TestReportManagerWithAggregationOperationType,
+                         Values(OperationType::single, OperationType::max,
+                                OperationType::min, OperationType::avg,
+                                OperationType::sum));
+
+TEST_P(TestReportManagerWithAggregationOperationType,
+       addReportWithDifferentOperationTypes)
+{
+    reportParams.readingParameters(
+        {{{sdbusplus::message::object_path(
+              "/xyz/openbmc_project/sensors/power/p1")},
+          utils::enumToString(operationType),
+          "MetricId1",
+          "Metadata1"}});
+
+    reportFactoryMock.expectMake(_, reportParams, Ref(*sut), Ref(storageMock))
+        .WillOnce(Return(ByMove(std::move(reportMockPtr))));
+
+    auto [ec, path] = addReport(reportParams);
+    EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
+    EXPECT_THAT(path, Eq("/"s + reportParams.reportName()));
+}
+
 class TestReportManagerStorage : public TestReportManager
 {
   public:
@@ -272,12 +305,9 @@
     {
         return utils::transform(params, [](const auto& item) {
             return LabeledMetricParameters(
-                utils::transform(std::get<0>(item),
-                                 [](const auto& elem) {
-                                     return LabeledSensorParameters("service",
-                                                                    elem);
-                                 }),
-                std::get<1>(item), std::get<2>(item), std::get<3>(item));
+                LabeledSensorParameters("service", std::get<0>(item)),
+                utils::stringToOperationType(std::get<1>(item)),
+                std::get<2>(item), std::get<3>(item));
         });
     }