1 #include "dbus_environment.hpp"
2 #include "helpers.hpp"
3 #include "interfaces/trigger_manager.hpp"
4 #include "mocks/json_storage_mock.hpp"
5 #include "mocks/report_factory_mock.hpp"
6 #include "mocks/trigger_manager_mock.hpp"
7 #include "params/report_params.hpp"
8 #include "report.hpp"
9 #include "report_manager.hpp"
10 #include "utils/conversion.hpp"
11 #include "utils/transform.hpp"
12 
13 using namespace testing;
14 using namespace std::string_literals;
15 using namespace std::chrono_literals;
16 
17 class TestReportManager : public Test
18 {
19   public:
20     ReportParams reportParams;
21 
22     std::unique_ptr<ReportFactoryMock> reportFactoryMockPtr =
23         std::make_unique<StrictMock<ReportFactoryMock>>();
24     ReportFactoryMock& reportFactoryMock = *reportFactoryMockPtr;
25 
26     std::unique_ptr<StorageMock> storageMockPtr =
27         std::make_unique<NiceMock<StorageMock>>();
28     StorageMock& storageMock = *storageMockPtr;
29 
30     std::unique_ptr<ReportMock> reportMockPtr =
31         std::make_unique<NiceMock<ReportMock>>(reportParams.reportId());
32     ReportMock& reportMock = *reportMockPtr;
33 
34     std::unique_ptr<ReportManager> sut;
35 
36     MockFunction<void(std::string)> checkPoint;
37 
38     void SetUp() override
39     {
40         EXPECT_CALL(reportFactoryMock, convertMetricParams(_, _))
41             .Times(AnyNumber());
42 
43         sut = std::make_unique<ReportManager>(std::move(reportFactoryMockPtr),
44                                               std::move(storageMockPtr),
45                                               DbusEnvironment::getObjServer());
46     }
47 
48     void TearDown() override
49     {
50         DbusEnvironment::synchronizeIoc();
51     }
52 
53     template <class... Args>
54     requires(sizeof...(Args) > 1)
55         std::pair<boost::system::error_code, std::string> addReport(
56             Args&&... args)
57     {
58         std::promise<std::pair<boost::system::error_code, std::string>>
59             addReportPromise;
60         DbusEnvironment::getBus()->async_method_call(
61             [&addReportPromise](boost::system::error_code ec,
62                                 const std::string& path) {
63                 addReportPromise.set_value({ec, path});
64             },
65             DbusEnvironment::serviceName(), ReportManager::reportManagerPath,
66             ReportManager::reportManagerIfaceName, "AddReportFutureVersion",
67             std::forward<Args>(args)...);
68         return DbusEnvironment::waitForFuture(addReportPromise.get_future());
69     }
70 
71     auto addReport(const ReportParams& params)
72     {
73         return addReport(params.reportId(), params.reportName(),
74                          utils::enumToString(params.reportingType()),
75                          utils::enumToString(params.reportUpdates()),
76                          params.appendLimit(),
77                          utils::transform(params.reportActions(),
78                                           [](const auto v) {
79                                               return utils::enumToString(v);
80                                           }),
81                          params.interval().count(),
82                          toReadingParameters(params.metricParameters()));
83     }
84 
85     template <class T>
86     static T getProperty(const std::string& property)
87     {
88         return DbusEnvironment::getProperty<T>(
89             ReportManager::reportManagerPath,
90             ReportManager::reportManagerIfaceName, property);
91     }
92 };
93 
94 TEST_F(TestReportManager, minInterval)
95 {
96     EXPECT_THAT(getProperty<uint64_t>("MinInterval"),
97                 Eq(ReportManager::minInterval.count()));
98 }
99 
100 TEST_F(TestReportManager, maxReports)
101 {
102     EXPECT_THAT(getProperty<size_t>("MaxReports"),
103                 Eq(ReportManager::maxReports));
104 }
105 
106 TEST_F(TestReportManager, returnsPropertySupportedOperationTypes)
107 {
108     EXPECT_THAT(
109         getProperty<std::vector<std::string>>("SupportedOperationTypes"),
110         UnorderedElementsAre("Maximum", "Minimum", "Average", "Summation"));
111 }
112 
113 TEST_F(TestReportManager, addReport)
114 {
115     EXPECT_CALL(reportFactoryMock, convertMetricParams(_, _));
116     reportFactoryMock.expectMake(reportParams, Ref(*sut), Ref(storageMock))
117         .WillOnce(Return(ByMove(std::move(reportMockPtr))));
118 
119     auto [ec, path] = addReport(reportParams);
120     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
121     EXPECT_THAT(path, Eq(reportMock.getPath()));
122 }
123 
124 TEST_F(TestReportManager, addOnChangeReport)
125 {
126     EXPECT_CALL(reportFactoryMock, convertMetricParams(_, _));
127     reportFactoryMock
128         .expectMake(reportParams.reportingType(ReportingType::onChange),
129                     Ref(*sut), Ref(storageMock))
130         .WillOnce(Return(ByMove(std::move(reportMockPtr))));
131 
132     auto [ec, path] = addReport(reportParams);
133     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
134     EXPECT_THAT(path, Eq(reportMock.getPath()));
135 }
136 
137 TEST_F(TestReportManager, nameIsUsedToGenerateIdWhenIdIsEmptyInAddReport)
138 {
139     reportParams.reportId("ReportName");
140     reportParams.reportName("ReportName");
141 
142     reportFactoryMock.expectMake(reportParams, Ref(*sut), Ref(storageMock));
143 
144     auto [ec, path] = addReport(reportParams.reportId(""));
145 
146     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
147     EXPECT_THAT(path, Eq("/ReportName"));
148 }
149 
150 TEST_F(TestReportManager, nameIsUsedToGenerateIdWhenIdIsNamespace)
151 {
152     reportParams.reportId("Prefix/ReportName");
153     reportParams.reportName("ReportName");
154 
155     reportFactoryMock.expectMake(reportParams, Ref(*sut), Ref(storageMock));
156 
157     auto [ec, path] = addReport(reportParams.reportId("Prefix/"));
158 
159     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
160     EXPECT_THAT(path, Eq("/Prefix/ReportName"));
161 }
162 
163 TEST_F(TestReportManager, addReportWithMaxLengthId)
164 {
165     std::string reportId(ReportManager::maxReportIdLength, 'z');
166     reportParams.reportId(reportId);
167     reportFactoryMock.expectMake(reportParams, Ref(*sut), Ref(storageMock));
168 
169     auto [ec, path] = addReport(reportParams);
170 
171     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
172     EXPECT_THAT(path, Eq("/"s + reportId));
173 }
174 
175 TEST_F(TestReportManager, DISABLED_failToAddReportWithTooLongName)
176 {
177     reportFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
178         .Times(0);
179 
180     reportParams.reportId(
181         std::string(ReportManager::maxReportIdLength + 1, 'z'));
182 
183     auto [ec, path] = addReport(reportParams);
184 
185     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
186     EXPECT_THAT(path, Eq(std::string()));
187 }
188 
189 TEST_F(TestReportManager, DISABLED_failToAddReportTwice)
190 {
191     reportFactoryMock.expectMake(reportParams, Ref(*sut), Ref(storageMock))
192         .WillOnce(Return(ByMove(std::move(reportMockPtr))));
193 
194     addReport(reportParams);
195 
196     auto [ec, path] = addReport(reportParams);
197 
198     EXPECT_THAT(ec.value(), Eq(boost::system::errc::file_exists));
199     EXPECT_THAT(path, Eq(std::string()));
200 }
201 
202 TEST_F(TestReportManager, DISABLED_failToAddReportWithInvalidInterval)
203 {
204     reportFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
205         .Times(0);
206 
207     reportParams.reportingType(ReportingType::periodic);
208     reportParams.interval(reportParams.interval() - 1ms);
209 
210     auto [ec, path] = addReport(reportParams);
211 
212     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
213     EXPECT_THAT(path, Eq(std::string()));
214 }
215 
216 TEST_F(TestReportManager, DISABLED_failToAddReportWithInvalidReportingType)
217 {
218     reportFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
219         .Times(0);
220 
221     auto [ec, path] = addReport(
222         reportParams.reportName(), "InvalidReportingType",
223         utils::transform(reportParams.reportActions(),
224                          [](const auto v) { return utils::enumToString(v); }),
225         reportParams.interval().count(),
226         toReadingParameters(reportParams.metricParameters()));
227 
228     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
229     EXPECT_THAT(path, Eq(std::string()));
230 }
231 
232 TEST_F(TestReportManager,
233        DISABLED_failToAddReportWithMoreMetricPropertiesThanExpected)
234 {
235     reportFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
236         .Times(0);
237 
238     reportParams.metricParameters(
239         std::vector<LabeledMetricParameters>{{LabeledMetricParameters{
240             {LabeledSensorInfo{"Service",
241                                "/xyz/openbmc_project/sensors/power/p1",
242                                "Metadata1"}},
243             OperationType::avg,
244             "MetricId1",
245             CollectionTimeScope::point,
246             CollectionDuration(Milliseconds(0u))}}});
247 
248     auto metricParams = reportParams.metricParameters();
249     auto& metricParamsVec =
250         metricParams[0].at_label<utils::tstring::SensorPath>();
251 
252     for (size_t i = 0; i < ReportManager::maxNumberMetrics; i++)
253     {
254         metricParamsVec.emplace_back(LabeledSensorInfo{
255             "Service", "/xyz/openbmc_project/sensors/power/p1", "Metadata1"});
256     }
257 
258     reportParams.metricParameters(std::move(metricParams));
259 
260     auto [ec, path] = addReport(reportParams);
261 
262     EXPECT_THAT(ec.value(), Eq(boost::system::errc::argument_list_too_long));
263     EXPECT_THAT(path, Eq(std::string()));
264 }
265 
266 TEST_F(TestReportManager, DISABLED_failToAddReportWithMoreMetricsThanExpected)
267 {
268     reportFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
269         .Times(0);
270 
271     auto metricParams = std::vector<LabeledMetricParameters>{};
272 
273     for (size_t i = 0; i < ReportManager::maxNumberMetrics + 1; i++)
274     {
275         metricParams.emplace_back(
276             LabeledMetricParameters{{},
277                                     OperationType::avg,
278                                     "MetricId1",
279                                     CollectionTimeScope::point,
280                                     CollectionDuration(Milliseconds(0u))});
281     }
282 
283     reportParams.metricParameters(std::move(metricParams));
284 
285     auto [ec, path] = addReport(reportParams);
286 
287     EXPECT_THAT(ec.value(), Eq(boost::system::errc::argument_list_too_long));
288     EXPECT_THAT(path, Eq(std::string()));
289 }
290 
291 TEST_F(TestReportManager, DISABLED_failToAddReportWithAppendLimitGreaterThanMax)
292 {
293     reportFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
294         .Times(0);
295 
296     reportParams.appendLimit(ReportManager::maxAppendLimit + 1);
297 
298     auto [ec, path] = addReport(reportParams);
299 
300     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
301     EXPECT_THAT(path, Eq(std::string()));
302 }
303 
304 TEST_F(TestReportManager, addReportWithAppendLimitEqualToUint64MaxIsAllowed)
305 {
306     reportParams.appendLimit(std::numeric_limits<uint64_t>::max());
307 
308     EXPECT_CALL(reportFactoryMock, convertMetricParams(_, _));
309     reportFactoryMock.expectMake(reportParams, Ref(*sut), Ref(storageMock))
310         .WillOnce(Return(ByMove(std::move(reportMockPtr))));
311 
312     auto [ec, path] = addReport(reportParams);
313     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
314     EXPECT_THAT(path, Eq(reportMock.getPath()));
315 }
316 
317 TEST_F(TestReportManager, DISABLED_failToAddReportWhenMaxReportIsReached)
318 {
319     reportFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
320         .Times(ReportManager::maxReports);
321 
322     for (size_t i = 0; i < ReportManager::maxReports; i++)
323     {
324         reportParams.reportId(reportParams.reportName() + std::to_string(i));
325 
326         auto [ec, path] = addReport(reportParams);
327         EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
328     }
329 
330     reportParams.reportId(reportParams.reportName() +
331                           std::to_string(ReportManager::maxReports));
332     auto [ec, path] = addReport(reportParams);
333 
334     EXPECT_THAT(ec.value(), Eq(boost::system::errc::too_many_files_open));
335     EXPECT_THAT(path, Eq(std::string()));
336 }
337 
338 TEST_F(TestReportManager, removeReport)
339 {
340     {
341         InSequence seq;
342         EXPECT_CALL(reportFactoryMock, convertMetricParams(_, _));
343         reportFactoryMock.expectMake(reportParams, Ref(*sut), Ref(storageMock))
344             .WillOnce(Return(ByMove(std::move(reportMockPtr))));
345         EXPECT_CALL(reportMock, Die());
346         EXPECT_CALL(checkPoint, Call("end"));
347     }
348 
349     addReport(reportParams);
350     sut->removeReport(&reportMock);
351     checkPoint.Call("end");
352 }
353 
354 TEST_F(TestReportManager, removingReportThatIsNotInContainerHasNoEffect)
355 {
356     {
357         InSequence seq;
358         EXPECT_CALL(checkPoint, Call("end"));
359         EXPECT_CALL(reportMock, Die());
360     }
361 
362     sut->removeReport(&reportMock);
363     checkPoint.Call("end");
364 }
365 
366 TEST_F(TestReportManager, removingSameReportTwiceHasNoSideEffect)
367 {
368     {
369         InSequence seq;
370         EXPECT_CALL(reportFactoryMock, convertMetricParams(_, _));
371         reportFactoryMock.expectMake(reportParams, Ref(*sut), Ref(storageMock))
372             .WillOnce(Return(ByMove(std::move(reportMockPtr))));
373         EXPECT_CALL(reportMock, Die());
374         EXPECT_CALL(checkPoint, Call("end"));
375     }
376 
377     addReport(reportParams);
378     sut->removeReport(&reportMock);
379     sut->removeReport(&reportMock);
380     checkPoint.Call("end");
381 }
382 
383 class TestReportManagerWithAggregationOperationType :
384     public TestReportManager,
385     public WithParamInterface<OperationType>
386 {
387   public:
388     OperationType operationType = GetParam();
389 };
390 
391 INSTANTIATE_TEST_SUITE_P(_, TestReportManagerWithAggregationOperationType,
392                          Values(OperationType::max, OperationType::min,
393                                 OperationType::avg, OperationType::sum));
394 
395 TEST_P(TestReportManagerWithAggregationOperationType,
396        addReportWithDifferentOperationTypes)
397 {
398     reportParams.metricParameters(
399         std::vector<LabeledMetricParameters>{{LabeledMetricParameters{
400             {LabeledSensorInfo{"Service",
401                                "/xyz/openbmc_project/sensors/power/p1",
402                                "Metadata1"}},
403             operationType,
404             "MetricId1",
405             CollectionTimeScope::point,
406             CollectionDuration(Milliseconds(0u))}}});
407 
408     reportFactoryMock.expectMake(reportParams, Ref(*sut), Ref(storageMock))
409         .WillOnce(Return(ByMove(std::move(reportMockPtr))));
410 
411     auto [ec, path] = addReport(reportParams);
412 
413     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
414     EXPECT_THAT(path, Eq("/"s + reportParams.reportId()));
415 }
416 
417 class TestReportManagerStorage : public TestReportManager
418 {
419   public:
420     using FilePath = interfaces::JsonStorage::FilePath;
421     using DirectoryPath = interfaces::JsonStorage::DirectoryPath;
422 
423     void SetUp() override
424     {
425         EXPECT_CALL(reportFactoryMock, convertMetricParams(_, _)).Times(0);
426 
427         ON_CALL(storageMock, list())
428             .WillByDefault(Return(std::vector<FilePath>{FilePath("report1")}));
429         ON_CALL(storageMock, load(FilePath("report1")))
430             .WillByDefault(InvokeWithoutArgs([this] { return data; }));
431     }
432 
433     void makeReportManager()
434     {
435         sut = std::make_unique<ReportManager>(std::move(reportFactoryMockPtr),
436                                               std::move(storageMockPtr),
437                                               DbusEnvironment::getObjServer());
438     }
439 
440     nlohmann::json data = nlohmann::json{
441         {"Enabled", reportParams.enabled()},
442         {"Version", Report::reportVersion},
443         {"Id", reportParams.reportId()},
444         {"Name", reportParams.reportName()},
445         {"ReportingType", utils::toUnderlying(reportParams.reportingType())},
446         {"ReportActions", reportParams.reportActions()},
447         {"Interval", reportParams.interval().count()},
448         {"ReportUpdates", utils::toUnderlying(reportParams.reportUpdates())},
449         {"AppendLimit", reportParams.appendLimit()},
450         {"ReadingParameters", reportParams.metricParameters()}};
451 };
452 
453 TEST_F(TestReportManagerStorage, reportManagerCtorAddReportFromStorage)
454 {
455     reportFactoryMock.expectMake(reportParams, _, Ref(storageMock));
456 
457     makeReportManager();
458 }
459 
460 TEST_F(TestReportManagerStorage,
461        reportManagerCtorRemoveFileIfVersionDoesNotMatch)
462 {
463     data["Version"] = Report::reportVersion - 1;
464 
465     EXPECT_CALL(storageMock, remove(FilePath("report1")));
466 
467     makeReportManager();
468 }
469 
470 TEST_F(TestReportManagerStorage,
471        reportManagerCtorRemoveFileIfIntervalHasWrongType)
472 {
473     data["Interval"] = "1000";
474 
475     EXPECT_CALL(storageMock, remove(FilePath("report1")));
476 
477     makeReportManager();
478 }
479