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/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");
+}