Add ReportManager and Report unit tests

Introduced ReportFactory to seperate Report and ReportManager
unit tests. Implemented mocks for Report, ReportManager and
ReportFactory classes. Added tests for DBus Properties and Methods
provided by telemetry service.

Tested:
 - Ran unit-tests with success

Change-Id: I1860e280d26ee4becc52de98dd65e5697d26b376
Signed-off-by: Wludzik, Jozef <jozef.wludzik@intel.com>
diff --git a/.gitignore b/.gitignore
index 5fc91fd..1ee5855 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 build/
-telemetry
 xyz.openbmc_project.Telemetry.service
+telemetry
 
 # Created by https://www.toptal.com/developers/gitignore/api/vim,intellij,meson,visualstudiocode
 # Edit at https://www.toptal.com/developers/gitignore?templates=vim,intellij,meson,visualstudiocode
diff --git a/meson.build b/meson.build
index d3472da..165eb2d 100644
--- a/meson.build
+++ b/meson.build
@@ -74,6 +74,7 @@
         'src/main.cpp',
         'src/persistent_json_storage.cpp',
         'src/report.cpp',
+        'src/report_factory.cpp',
         'src/report_manager.cpp',
         'src/sensor.cpp',
         'src/sensor_cache.cpp',
diff --git a/src/interfaces/report.hpp b/src/interfaces/report.hpp
new file mode 100644
index 0000000..98421be
--- /dev/null
+++ b/src/interfaces/report.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <string>
+
+namespace interfaces
+{
+
+class Report
+{
+  public:
+    virtual ~Report() = default;
+
+    virtual std::string getName() const = 0;
+    virtual std::string getPath() const = 0;
+};
+} // namespace interfaces
diff --git a/src/interfaces/report_factory.hpp b/src/interfaces/report_factory.hpp
new file mode 100644
index 0000000..9273d03
--- /dev/null
+++ b/src/interfaces/report_factory.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "interfaces/report.hpp"
+#include "interfaces/report_manager.hpp"
+#include "interfaces/types.hpp"
+
+#include <chrono>
+#include <memory>
+
+class ReportManager;
+
+namespace interfaces
+{
+
+class ReportFactory
+{
+  public:
+    virtual ~ReportFactory() = default;
+
+    virtual std::unique_ptr<interfaces::Report>
+        make(const std::string& name, const std::string& reportingType,
+             bool emitsReadingsSignal, bool logToMetricReportsCollection,
+             std::chrono::milliseconds period,
+             const ReadingParameters& metricParams,
+             ReportManager& reportManager) const = 0;
+};
+
+} // namespace interfaces
diff --git a/src/interfaces/report_manager.hpp b/src/interfaces/report_manager.hpp
new file mode 100644
index 0000000..80e0c82
--- /dev/null
+++ b/src/interfaces/report_manager.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "interfaces/report.hpp"
+
+namespace interfaces
+{
+
+class ReportManager
+{
+  public:
+    virtual ~ReportManager() = default;
+
+    virtual void removeReport(const interfaces::Report* report) = 0;
+};
+
+} // namespace interfaces
diff --git a/src/interfaces/types.hpp b/src/interfaces/types.hpp
new file mode 100644
index 0000000..09a75cd
--- /dev/null
+++ b/src/interfaces/types.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <sdbusplus/message/types.hpp>
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+using ReadingParameters =
+    std::vector<std::tuple<std::vector<sdbusplus::message::object_path>,
+                           std::string, std::string, std::string>>;
diff --git a/src/report.cpp b/src/report.cpp
index 45639aa..81e3b05 100644
--- a/src/report.cpp
+++ b/src/report.cpp
@@ -2,9 +2,9 @@
 
 #include "report_manager.hpp"
 
-constexpr const char* reportIfaceName = "xyz.openbmc_project.Telemetry.Report";
-constexpr const char* reportPath = "/xyz/openbmc_project/Telemetry/Reports/";
-constexpr const char* deleteIfaceName = "xyz.openbmc_project.Object.Delete";
+using Readings = std::tuple<
+    uint64_t,
+    std::vector<std::tuple<std::string, std::string, double, uint64_t>>>;
 
 Report::Report(boost::asio::io_context& ioc,
                const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
@@ -13,9 +13,9 @@
                const bool logToMetricReportsCollection,
                const std::chrono::milliseconds period,
                const ReadingParameters& metricParams,
-               ReportManager& reportManager) :
+               interfaces::ReportManager& reportManager) :
     name{reportName},
-    path{reportPath + name}, interval{period}, objServer(objServer)
+    path{reportDir + name}, interval{period}, objServer(objServer)
 {
     reportIface = objServer->add_unique_interface(
         path, reportIfaceName,
diff --git a/src/report.hpp b/src/report.hpp
index 7f74d49..fb40614 100644
--- a/src/report.hpp
+++ b/src/report.hpp
@@ -1,30 +1,26 @@
 #pragma once
 
+#include "interfaces/report.hpp"
+#include "interfaces/report_manager.hpp"
+#include "interfaces/types.hpp"
+
 #include <boost/asio/io_context.hpp>
 #include <sdbusplus/asio/object_server.hpp>
 
 #include <chrono>
 #include <memory>
 
-using Readings = std::tuple<
-    uint64_t,
-    std::vector<std::tuple<std::string, std::string, double, uint64_t>>>;
-using ReadingParameters =
-    std::vector<std::tuple<std::vector<sdbusplus::message::object_path>,
-                           std::string, std::string, std::string>>;
-
-class ReportManager;
-
-class Report
+class Report : public interfaces::Report
 {
   public:
     Report(boost::asio::io_context& ioc,
            const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
-           const std::string& name, const std::string& reportingType,
+           const std::string& reportName, const std::string& reportingType,
            const bool emitsReadingsSignal,
            const bool logToMetricReportsCollection,
            const std::chrono::milliseconds period,
-           const ReadingParameters& metricParams, ReportManager& reportManager);
+           const ReadingParameters& metricParams,
+           interfaces::ReportManager& reportManager);
     ~Report() = default;
 
     Report(Report&) = delete;
@@ -32,12 +28,29 @@
     Report& operator=(Report&) = delete;
     Report& operator=(Report&&) = delete;
 
-    const std::string name;
-    const std::string path;
+    std::string getName() const override
+    {
+        return name;
+    }
+
+    std::string getPath() const override
+    {
+        return path;
+    }
 
   private:
+    const std::string name;
+    const std::string path;
     std::chrono::milliseconds interval;
     std::shared_ptr<sdbusplus::asio::object_server> objServer;
     std::unique_ptr<sdbusplus::asio::dbus_interface> reportIface;
     std::unique_ptr<sdbusplus::asio::dbus_interface> deleteIface;
+
+  public:
+    static constexpr const char* reportIfaceName =
+        "xyz.openbmc_project.Telemetry.Report";
+    static constexpr const char* reportDir =
+        "/xyz/openbmc_project/Telemetry/Reports/";
+    static constexpr const char* deleteIfaceName =
+        "xyz.openbmc_project.Object.Delete";
 };
diff --git a/src/report_factory.cpp b/src/report_factory.cpp
new file mode 100644
index 0000000..9f51db5
--- /dev/null
+++ b/src/report_factory.cpp
@@ -0,0 +1,21 @@
+#include "report_factory.hpp"
+
+#include "report.hpp"
+
+ReportFactory::ReportFactory(
+    boost::asio::io_context& ioc,
+    const std::shared_ptr<sdbusplus::asio::object_server>& objServer) :
+    ioc(ioc),
+    objServer(objServer)
+{}
+
+std::unique_ptr<interfaces::Report> ReportFactory::make(
+    const std::string& name, const std::string& reportingType,
+    bool emitsReadingsSignal, bool logToMetricReportsCollection,
+    std::chrono::milliseconds period, const ReadingParameters& metricParams,
+    interfaces::ReportManager& reportManager) const
+{
+    return std::make_unique<Report>(
+        ioc, objServer, name, reportingType, emitsReadingsSignal,
+        logToMetricReportsCollection, period, metricParams, reportManager);
+}
diff --git a/src/report_factory.hpp b/src/report_factory.hpp
new file mode 100644
index 0000000..775ea45
--- /dev/null
+++ b/src/report_factory.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "interfaces/report_factory.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+class ReportFactory : public interfaces::ReportFactory
+{
+  public:
+    ReportFactory(
+        boost::asio::io_context& ioc,
+        const std::shared_ptr<sdbusplus::asio::object_server>& objServer);
+
+    std::unique_ptr<interfaces::Report>
+        make(const std::string& name, const std::string& reportingType,
+             bool emitsReadingsSignal, bool logToMetricReportsCollection,
+             std::chrono::milliseconds period,
+             const ReadingParameters& metricParams,
+             interfaces::ReportManager& reportManager) const override;
+
+  private:
+    boost::asio::io_context& ioc;
+    std::shared_ptr<sdbusplus::asio::object_server> objServer;
+};
diff --git a/src/report_manager.cpp b/src/report_manager.cpp
index f2cbe94..5944fa9 100644
--- a/src/report_manager.cpp
+++ b/src/report_manager.cpp
@@ -4,21 +4,16 @@
 
 #include <system_error>
 
-constexpr const char* reportManagerIfaceName =
-    "xyz.openbmc_project.Telemetry.ReportManager";
-constexpr const char* reportManagerPath =
-    "/xyz/openbmc_project/Telemetry/Reports";
-
 ReportManager::ReportManager(
-    const std::shared_ptr<sdbusplus::asio::connection>& bus,
+    std::unique_ptr<interfaces::ReportFactory> reportFactoryIn,
     const std::shared_ptr<sdbusplus::asio::object_server>& objServerIn) :
+    reportFactory(std::move(reportFactoryIn)),
     objServer(objServerIn)
 {
     reports.reserve(maxReports);
 
     reportManagerIface = objServer->add_unique_interface(
-        reportManagerPath, reportManagerIfaceName,
-        [this, &ioc = bus->get_io_context()](auto& dbusIface) {
+        reportManagerPath, reportManagerIfaceName, [this](auto& dbusIface) {
             dbusIface.register_property_r(
                 "MaxReports", uint32_t{}, sdbusplus::vtable::property_::const_,
                 [](const auto&) { return maxReports; });
@@ -27,13 +22,12 @@
                 [](const auto&) -> uint64_t { return minInterval.count(); });
 
             dbusIface.register_method(
-                "AddReport",
-                [this, &ioc](const std::string& reportName,
-                             const std::string& reportingType,
-                             const bool emitsReadingsUpdate,
-                             const bool logToMetricReportsCollection,
-                             const uint64_t interval,
-                             const ReadingParameters& metricParams) {
+                "AddReport", [this](const std::string& reportName,
+                                    const std::string& reportingType,
+                                    const bool emitsReadingsUpdate,
+                                    const bool logToMetricReportsCollection,
+                                    const uint64_t interval,
+                                    const ReadingParameters& metricParams) {
                     if (reports.size() >= maxReports)
                     {
                         throw sdbusplus::exception::SdBusError(
@@ -43,7 +37,7 @@
 
                     for (const auto& report : reports)
                     {
-                        if (report->name == reportName)
+                        if (report->getName() == reportName)
                         {
                             throw sdbusplus::exception::SdBusError(
                                 static_cast<int>(std::errc::file_exists),
@@ -59,16 +53,16 @@
                             "Invalid interval");
                     }
 
-                    reports.emplace_back(std::make_unique<Report>(
-                        ioc, objServer, reportName, reportingType,
-                        emitsReadingsUpdate, logToMetricReportsCollection,
-                        std::move(reportInterval), metricParams, *this));
-                    return reports.back()->path;
+                    reports.emplace_back(reportFactory->make(
+                        reportName, reportingType, emitsReadingsUpdate,
+                        logToMetricReportsCollection, std::move(reportInterval),
+                        metricParams, *this));
+                    return reports.back()->getPath();
                 });
         });
 }
 
-void ReportManager::removeReport(const Report* report)
+void ReportManager::removeReport(const interfaces::Report* report)
 {
     reports.erase(
         std::remove_if(reports.begin(), reports.end(),
diff --git a/src/report_manager.hpp b/src/report_manager.hpp
index 177a03c..2427b76 100644
--- a/src/report_manager.hpp
+++ b/src/report_manager.hpp
@@ -1,6 +1,8 @@
 #pragma once
 
-#include "report.hpp"
+#include "interfaces/report.hpp"
+#include "interfaces/report_factory.hpp"
+#include "interfaces/report_manager.hpp"
 
 #include <sdbusplus/asio/object_server.hpp>
 
@@ -8,11 +10,11 @@
 #include <memory>
 #include <vector>
 
-class ReportManager
+class ReportManager : public interfaces::ReportManager
 {
   public:
     ReportManager(
-        const std::shared_ptr<sdbusplus::asio::connection>& bus,
+        std::unique_ptr<interfaces::ReportFactory> reportFactory,
         const std::shared_ptr<sdbusplus::asio::object_server>& objServer);
     ~ReportManager() = default;
 
@@ -21,15 +23,20 @@
     ReportManager& operator=(ReportManager&) = delete;
     ReportManager& operator=(ReportManager&&) = delete;
 
+    void removeReport(const interfaces::Report* report) override;
     static bool verifyScanPeriod(const uint64_t scanPeriod);
-    void removeReport(const Report* report);
 
   private:
+    std::unique_ptr<interfaces::ReportFactory> reportFactory;
     std::shared_ptr<sdbusplus::asio::object_server> objServer;
     std::unique_ptr<sdbusplus::asio::dbus_interface> reportManagerIface;
-    std::vector<std::unique_ptr<Report>> reports;
+    std::vector<std::unique_ptr<interfaces::Report>> reports;
 
   public:
     static constexpr uint32_t maxReports{20};
     static constexpr std::chrono::milliseconds minInterval{1000};
+    static constexpr const char* reportManagerIfaceName =
+        "xyz.openbmc_project.Telemetry.ReportManager";
+    static constexpr const char* reportManagerPath =
+        "/xyz/openbmc_project/Telemetry/Reports";
 };
diff --git a/src/telemetry.hpp b/src/telemetry.hpp
index efdebaa..bfcc99a 100644
--- a/src/telemetry.hpp
+++ b/src/telemetry.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "report_factory.hpp"
 #include "report_manager.hpp"
 
 #include <sdbusplus/asio/connection.hpp>
@@ -12,7 +13,9 @@
   public:
     Telemetry(std::shared_ptr<sdbusplus::asio::connection> bus) :
         objServer(std::make_shared<sdbusplus::asio::object_server>(bus)),
-        reportManager(bus, objServer)
+        reportManager(
+            std::make_unique<ReportFactory>(bus->get_io_context(), objServer),
+            objServer)
     {}
 
   private:
diff --git a/tests/meson.build b/tests/meson.build
index 89e5088..fbee253 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -25,6 +25,7 @@
         [
             '../src/persistent_json_storage.cpp',
             '../src/report.cpp',
+            '../src/report_factory.cpp',
             '../src/report_manager.cpp',
             '../src/sensor.cpp',
             '../src/sensor_cache.cpp',
@@ -33,6 +34,8 @@
             'src/stubs/dbus_sensor_object.cpp',
             'src/test_detached_timer.cpp',
             'src/test_persistent_json_storage.cpp',
+            'src/test_report.cpp',
+            'src/test_report_manager.cpp',
             'src/test_sensor.cpp',
             'src/test_sensor_cache.cpp',
             'src/test_unique_call.cpp',
diff --git a/tests/src/dbus_environment.hpp b/tests/src/dbus_environment.hpp
index d039922..e483fe7 100644
--- a/tests/src/dbus_environment.hpp
+++ b/tests/src/dbus_environment.hpp
@@ -1,4 +1,5 @@
 #include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/asio/property.hpp>
 
 #include <future>
 #include <thread>
diff --git a/tests/src/mocks/report_factory_mock.hpp b/tests/src/mocks/report_factory_mock.hpp
new file mode 100644
index 0000000..24cc23e
--- /dev/null
+++ b/tests/src/mocks/report_factory_mock.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "interfaces/report_factory.hpp"
+#include "mocks/report_mock.hpp"
+
+#include <gmock/gmock.h>
+
+class ReportFactoryMock : public interfaces::ReportFactory
+{
+  public:
+    ReportFactoryMock()
+    {
+        using namespace testing;
+
+        ON_CALL(*this, make)
+            .WillByDefault(WithArgs<0>(Invoke([](const std::string& name) {
+                return std::make_unique<NiceMock<ReportMock>>(name);
+            })));
+    }
+
+    MOCK_METHOD(std::unique_ptr<interfaces::Report>, make,
+                (const std::string& name, const std::string& reportingType,
+                 bool emitsReadingsSignal, bool logToMetricReportsCollection,
+                 std::chrono::milliseconds period,
+                 const ReadingParameters& metricParams,
+                 interfaces::ReportManager& reportManager),
+                (const, override));
+};
diff --git a/tests/src/mocks/report_manager_mock.hpp b/tests/src/mocks/report_manager_mock.hpp
new file mode 100644
index 0000000..17d0a06
--- /dev/null
+++ b/tests/src/mocks/report_manager_mock.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "interfaces/report_manager.hpp"
+
+#include <gmock/gmock.h>
+
+class ReportManagerMock : public interfaces::ReportManager
+{
+  public:
+    ReportManagerMock()
+    {}
+
+    MOCK_METHOD(void, removeReport, (const interfaces::Report*), (override));
+};
diff --git a/tests/src/mocks/report_mock.hpp b/tests/src/mocks/report_mock.hpp
new file mode 100644
index 0000000..4943421
--- /dev/null
+++ b/tests/src/mocks/report_mock.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "interfaces/report.hpp"
+
+#include <gmock/gmock.h>
+
+class ReportMock : public interfaces::Report
+{
+  public:
+    ReportMock(std::string reportName)
+    {
+        using namespace testing;
+
+        ON_CALL(*this, getName).WillByDefault([reportName] {
+            return reportName;
+        });
+        ON_CALL(*this, getPath).WillByDefault([reportName] {
+            return "/" + reportName;
+        });
+
+        EXPECT_CALL(*this, getPath).Times(AnyNumber());
+        EXPECT_CALL(*this, getName).Times(AnyNumber());
+        EXPECT_CALL(*this, Die).Times(AnyNumber());
+    }
+
+    virtual ~ReportMock()
+    {
+        Die();
+    }
+
+    MOCK_METHOD(std::string, getName, (), (override, const));
+    MOCK_METHOD(std::string, getPath, (), (override, const));
+    MOCK_METHOD(void, Die, ());
+};
diff --git a/tests/src/test_report.cpp b/tests/src/test_report.cpp
new file mode 100644
index 0000000..dea453d
--- /dev/null
+++ b/tests/src/test_report.cpp
@@ -0,0 +1,169 @@
+#include "dbus_environment.hpp"
+#include "mocks/report_manager_mock.hpp"
+#include "report.hpp"
+#include "report_manager.hpp"
+
+#include <sdbusplus/exception.hpp>
+
+using namespace testing;
+using namespace std::literals::string_literals;
+
+class TestReport : public Test
+{
+  public:
+    std::string defaultReportName = "TestReport";
+    std::string defaultReportType = "Periodic";
+    bool defaultEmitReadingSignal = true;
+    bool defaultLogToMetricReportCollection = true;
+    uint64_t defaultInterval = ReportManager::minInterval.count();
+    ReadingParameters defaultReadingParams = {};
+
+    std::unique_ptr<ReportManagerMock> reportManagerMock =
+        std::make_unique<StrictMock<ReportManagerMock>>();
+    Report sut =
+        Report(DbusEnvironment::getIoc(), DbusEnvironment::getObjServer(),
+               defaultReportName, defaultReportType, defaultEmitReadingSignal,
+               defaultLogToMetricReportCollection,
+               std::chrono::milliseconds{defaultInterval}, defaultReadingParams,
+               *reportManagerMock);
+
+    template <class T>
+    static T getProperty(const std::string& path, const std::string& property)
+    {
+        std::promise<T> propertyPromise;
+        sdbusplus::asio::getProperty<T>(
+            *DbusEnvironment::getBus(), DbusEnvironment::serviceName(), path,
+            Report::reportIfaceName, property,
+            [&propertyPromise](boost::system::error_code ec) {
+                EXPECT_THAT(static_cast<bool>(ec), ::testing::Eq(false));
+                propertyPromise.set_value(T{});
+            },
+            [&propertyPromise](T t) { propertyPromise.set_value(t); });
+        return DbusEnvironment::waitForFuture(propertyPromise.get_future())
+            .value_or(T{});
+    }
+
+    template <class T>
+    static boost::system::error_code setProperty(const std::string& path,
+                                                 const std::string& property,
+                                                 const T& newValue)
+    {
+        std::promise<boost::system::error_code> setPromise;
+        sdbusplus::asio::setProperty(
+            *DbusEnvironment::getBus(), DbusEnvironment::serviceName(), path,
+            Report::reportIfaceName, property, std::move(newValue),
+            [&setPromise](boost::system::error_code ec) {
+                setPromise.set_value(ec);
+            },
+            [&setPromise]() {
+                setPromise.set_value(boost::system::error_code{});
+            });
+        return DbusEnvironment::waitForFuture(setPromise.get_future())
+            .value_or(boost::system::error_code{});
+    }
+
+    boost::system::error_code deleteReport(const std::string& path)
+    {
+        std::promise<boost::system::error_code> deleteReportPromise;
+        DbusEnvironment::getBus()->async_method_call(
+            [&deleteReportPromise](boost::system::error_code ec) {
+                deleteReportPromise.set_value(ec);
+            },
+            DbusEnvironment::serviceName(), path, Report::deleteIfaceName,
+            "Delete");
+        return DbusEnvironment::waitForFuture(deleteReportPromise.get_future())
+            .value_or(boost::system::error_code{});
+    }
+};
+
+TEST_F(TestReport, verifyIfPropertiesHaveValidValue)
+{
+    EXPECT_THAT(getProperty<uint64_t>(sut.getPath(), "Interval"),
+                Eq(defaultInterval));
+    EXPECT_THAT(getProperty<bool>(sut.getPath(), "Persistency"), Eq(false));
+    EXPECT_THAT(getProperty<std::string>(sut.getPath(), "ReportingType"),
+                Eq(defaultReportType));
+    EXPECT_THAT(getProperty<bool>(sut.getPath(), "EmitsReadingsUpdate"),
+                Eq(defaultEmitReadingSignal));
+    EXPECT_THAT(
+        getProperty<bool>(sut.getPath(), "LogToMetricReportsCollection"),
+        Eq(defaultLogToMetricReportCollection));
+    EXPECT_THAT(
+        getProperty<ReadingParameters>(sut.getPath(), "ReadingParameters"),
+        Eq(defaultReadingParams));
+}
+
+TEST_F(TestReport, setIntervalWithValidValue)
+{
+    uint64_t newValue = defaultInterval + 1;
+    EXPECT_THAT(setProperty(sut.getPath(), "Interval", newValue).value(),
+                Eq(boost::system::errc::success));
+    EXPECT_THAT(getProperty<uint64_t>(sut.getPath(), "Interval"), Eq(newValue));
+}
+
+TEST_F(TestReport, settingIntervalWithInvalidValueDoesNotChangeProperty)
+{
+    uint64_t newValue = defaultInterval - 1;
+    EXPECT_THAT(setProperty(sut.getPath(), "Interval", newValue).value(),
+                Eq(boost::system::errc::success));
+    EXPECT_THAT(getProperty<uint64_t>(sut.getPath(), "Interval"),
+                Eq(defaultInterval));
+}
+
+TEST_F(TestReport, deleteReport)
+{
+    EXPECT_CALL(*reportManagerMock, removeReport(&sut));
+    auto ec = deleteReport(sut.getPath());
+    EXPECT_THAT(ec, Eq(boost::system::errc::success));
+}
+
+TEST_F(TestReport, deletingNonExistingReportReturnInvalidRequestDescriptor)
+{
+    auto ec = deleteReport(Report::reportDir + "NonExisting"s);
+    EXPECT_THAT(ec.value(), Eq(EBADR));
+}
+
+class TestReportCreation : public Test
+{
+  public:
+    std::unique_ptr<ReportManagerMock> reportManagerMock =
+        std::make_unique<StrictMock<ReportManagerMock>>();
+
+    std::unique_ptr<Report> createReportWithName(std::string name)
+    {
+        return std::make_unique<Report>(
+            DbusEnvironment::getIoc(), DbusEnvironment::getObjServer(), name,
+            "", true, true,
+            std::chrono::milliseconds{ReportManager::minInterval.count()},
+            ReadingParameters{}, *reportManagerMock);
+    }
+};
+
+class TestReportValidNames :
+    public TestReportCreation,
+    public WithParamInterface<const char*>
+{};
+
+INSTANTIATE_TEST_SUITE_P(ValidNames, TestReportValidNames,
+                         ValuesIn({"Valid_1", "Valid_1/Valid_2",
+                                   "Valid_1/Valid_2/Valid_3"}));
+
+TEST_P(TestReportValidNames, reportCtorDoesNotThrowOnValidName)
+{
+    EXPECT_NO_THROW(createReportWithName(GetParam()));
+}
+
+class TestReportInvalidNames :
+    public TestReportCreation,
+    public WithParamInterface<const char*>
+{};
+
+INSTANTIATE_TEST_SUITE_P(InvalidNames, TestReportInvalidNames,
+                         ValuesIn({"/", "/Invalid", "Invalid/",
+                                   "Invalid/Invalid/", "Invalid?"}));
+
+TEST_P(TestReportInvalidNames, reportCtorThrowOnInvalidName)
+{
+    EXPECT_THROW(createReportWithName(GetParam()),
+                 sdbusplus::exception::SdBusError);
+}
diff --git a/tests/src/test_report_manager.cpp b/tests/src/test_report_manager.cpp
new file mode 100644
index 0000000..561b4c8
--- /dev/null
+++ b/tests/src/test_report_manager.cpp
@@ -0,0 +1,194 @@
+#include "dbus_environment.hpp"
+#include "mocks/report_factory_mock.hpp"
+#include "report_manager.hpp"
+
+using namespace testing;
+
+class TestReportManager : public Test
+{
+  public:
+    std::string defaultReportName = "TestReport";
+    std::string defaultReportType = "Periodic";
+    bool defaultEmitReadingSignal = true;
+    bool defaultLogToMetricReportCollection = true;
+    uint64_t defaultInterval = ReportManager::minInterval.count();
+    ReadingParameters defaultReadingParams = {};
+
+    std::unique_ptr<ReportFactoryMock> reportFactoryMockPtr =
+        std::make_unique<StrictMock<ReportFactoryMock>>();
+    ReportFactoryMock& reportFactoryMock = *reportFactoryMockPtr;
+    ReportManager sut = ReportManager(std::move(reportFactoryMockPtr),
+                                      DbusEnvironment::getObjServer());
+
+    MockFunction<void(std::string)> checkPoint;
+
+    void TearDown() override
+    {
+        DbusEnvironment::synchronizeIoc();
+    }
+
+    std::pair<boost::system::error_code, std::string>
+        addReport(const std::string& reportName,
+                  uint64_t interval = ReportManager::minInterval.count())
+    {
+        std::promise<std::pair<boost::system::error_code, std::string>>
+            addReportPromise;
+        DbusEnvironment::getBus()->async_method_call(
+            [&addReportPromise](boost::system::error_code ec,
+                                const std::string& path) {
+                addReportPromise.set_value({ec, path});
+            },
+            DbusEnvironment::serviceName(), ReportManager::reportManagerPath,
+            ReportManager::reportManagerIfaceName, "AddReport", reportName,
+            defaultReportType, defaultEmitReadingSignal,
+            defaultLogToMetricReportCollection, interval, defaultReadingParams);
+        return DbusEnvironment::waitForFuture(addReportPromise.get_future())
+            .value_or(std::pair<boost::system::error_code, std::string>{});
+    }
+
+    template <class T>
+    static T getProperty(std::string property)
+    {
+        std::promise<T> propertyPromise;
+        sdbusplus::asio::getProperty<T>(
+            *DbusEnvironment::getBus(), DbusEnvironment::serviceName(),
+            ReportManager::reportManagerPath,
+            ReportManager::reportManagerIfaceName, property,
+            [&propertyPromise](boost::system::error_code ec) {
+                EXPECT_THAT(static_cast<bool>(ec), ::testing::Eq(false));
+                propertyPromise.set_value(T{});
+            },
+            [&propertyPromise](T t) { propertyPromise.set_value(t); });
+        return DbusEnvironment::waitForFuture(propertyPromise.get_future())
+            .value_or(T{});
+    }
+};
+
+TEST_F(TestReportManager, minInterval)
+{
+    EXPECT_THAT(getProperty<uint64_t>("MinInterval"),
+                Eq(static_cast<uint64_t>(ReportManager::minInterval.count())));
+}
+
+TEST_F(TestReportManager, maxReports)
+{
+    EXPECT_THAT(getProperty<uint32_t>("MaxReports"),
+                Eq(ReportManager::maxReports));
+}
+
+TEST_F(TestReportManager, addReport)
+{
+    auto reportMockPtr =
+        std::make_unique<NiceMock<ReportMock>>(defaultReportName);
+    auto& reportMock = *reportMockPtr;
+
+    EXPECT_CALL(reportFactoryMock,
+                make(defaultReportName, defaultReportType,
+                     defaultEmitReadingSignal,
+                     defaultLogToMetricReportCollection,
+                     std::chrono::milliseconds{defaultInterval},
+                     defaultReadingParams, Ref(sut)))
+        .WillOnce(Return(ByMove(std::move(reportMockPtr))));
+
+    auto [ec, path] = addReport(defaultReportName);
+    EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
+    EXPECT_THAT(path, Eq(reportMock.getPath()));
+}
+
+TEST_F(TestReportManager, failToAddReportTwice)
+{
+    EXPECT_CALL(reportFactoryMock, make(_, _, _, _, _, _, _));
+
+    addReport(defaultReportName);
+
+    auto [ec, path] = addReport(defaultReportName);
+    EXPECT_THAT(ec.value(), Eq(boost::system::errc::file_exists));
+    EXPECT_THAT(path, Eq(std::string()));
+}
+
+TEST_F(TestReportManager, failToAddReportWithInvalidInterval)
+{
+    EXPECT_CALL(reportFactoryMock, make(_, _, _, _, _, _, _)).Times(0);
+
+    uint64_t interval = defaultInterval - 1;
+
+    auto [ec, path] = addReport(defaultReportName, interval);
+    EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
+    EXPECT_THAT(path, Eq(std::string()));
+}
+
+TEST_F(TestReportManager, failToAddReportWhenMaxReportIsReached)
+{
+    EXPECT_CALL(reportFactoryMock, make(_, _, _, _, _, _, _))
+        .Times(ReportManager::maxReports);
+
+    for (size_t i = 0; i < ReportManager::maxReports; i++)
+    {
+        std::string reportName = defaultReportName + std::to_string(i);
+
+        auto [ec, path] = addReport(reportName);
+        EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
+    }
+
+    std::string reportName =
+        defaultReportName + std::to_string(ReportManager::maxReports);
+    auto [ec, path] = addReport(reportName);
+    EXPECT_THAT(ec.value(), Eq(boost::system::errc::too_many_files_open));
+    EXPECT_THAT(path, Eq(std::string()));
+}
+
+TEST_F(TestReportManager, removeReport)
+{
+    auto reportMockPtr =
+        std::make_unique<NiceMock<ReportMock>>(defaultReportName);
+    auto& reportMock = *reportMockPtr;
+
+    {
+        InSequence seq;
+        EXPECT_CALL(reportFactoryMock, make(_, _, _, _, _, _, _))
+            .WillOnce(Return(ByMove(std::move(reportMockPtr))));
+        EXPECT_CALL(reportMock, Die());
+        EXPECT_CALL(checkPoint, Call("end"));
+    }
+
+    addReport(defaultReportName);
+    sut.removeReport(&reportMock);
+    checkPoint.Call("end");
+}
+
+TEST_F(TestReportManager, removingReportThatIsNotInContainerHasNoEffect)
+{
+    auto reportMockPtr =
+        std::make_unique<NiceMock<ReportMock>>(defaultReportName);
+    auto& reportMock = *reportMockPtr;
+
+    {
+        InSequence seq;
+        EXPECT_CALL(checkPoint, Call("end"));
+        EXPECT_CALL(reportMock, Die());
+    }
+
+    sut.removeReport(&reportMock);
+    checkPoint.Call("end");
+}
+
+TEST_F(TestReportManager, removingSameReportTwiceHasNoSideEffect)
+{
+    auto reportMockPtr =
+        std::make_unique<NiceMock<ReportMock>>(defaultReportName);
+    auto& reportMock = *reportMockPtr;
+
+    {
+        InSequence seq;
+        EXPECT_CALL(reportFactoryMock,
+                    make(defaultReportName, _, _, _, _, _, _))
+            .WillOnce(Return(ByMove(std::move(reportMockPtr))));
+        EXPECT_CALL(reportMock, Die());
+        EXPECT_CALL(checkPoint, Call("end"));
+    }
+
+    addReport(defaultReportName);
+    sut.removeReport(&reportMock);
+    sut.removeReport(&reportMock);
+    checkPoint.Call("end");
+}