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