1 #include "dbus_environment.hpp"
2 #include "helpers.hpp"
3 #include "messages/collect_trigger_id.hpp"
4 #include "messages/trigger_presence_changed_ind.hpp"
5 #include "mocks/json_storage_mock.hpp"
6 #include "mocks/report_manager_mock.hpp"
7 #include "mocks/sensor_mock.hpp"
8 #include "mocks/threshold_mock.hpp"
9 #include "mocks/trigger_factory_mock.hpp"
10 #include "mocks/trigger_manager_mock.hpp"
11 #include "params/trigger_params.hpp"
12 #include "trigger.hpp"
13 #include "trigger_manager.hpp"
14 #include "utils/conversion_trigger.hpp"
15 #include "utils/messanger.hpp"
16 #include "utils/transform.hpp"
17 #include "utils/tstring.hpp"
18 
19 #include <boost/range/combine.hpp>
20 
21 using namespace testing;
22 using namespace std::literals::string_literals;
23 using sdbusplus::message::object_path;
24 
25 static constexpr size_t expectedTriggerVersion = 2;
26 
27 class TestTrigger : public Test
28 {
29   public:
30     TriggerParams triggerParams;
31     TriggerParams triggerDiscreteParams =
32         TriggerParams()
33             .id("DiscreteTrigger")
34             .name("My Discrete Trigger")
35             .thresholdParams(std::vector<discrete::LabeledThresholdParam>{
36                 discrete::LabeledThresholdParam{
37                     "userId", discrete::Severity::warning,
38                     Milliseconds(10).count(), "15.2"},
39                 discrete::LabeledThresholdParam{
40                     "userId_2", discrete::Severity::critical,
41                     Milliseconds(5).count(), "32.7"},
42             });
43 
44     std::unique_ptr<ReportManagerMock> reportManagerMockPtr =
45         std::make_unique<NiceMock<ReportManagerMock>>();
46     std::unique_ptr<TriggerManagerMock> triggerManagerMockPtr =
47         std::make_unique<NiceMock<TriggerManagerMock>>();
48     std::unique_ptr<TriggerFactoryMock> triggerFactoryMockPtr =
49         std::make_unique<NiceMock<TriggerFactoryMock>>();
50     testing::NiceMock<StorageMock> storageMock;
51     NiceMock<MockFunction<void(const messages::TriggerPresenceChangedInd)>>
52         triggerPresenceChanged;
53     std::vector<std::shared_ptr<interfaces::Threshold>> thresholdMocks;
54     utils::Messanger messanger;
55     std::unique_ptr<Trigger> sut;
56 
57     TestTrigger() : messanger(DbusEnvironment::getIoc())
58     {
59         messanger.on_receive<messages::TriggerPresenceChangedInd>(
60             [this](const auto& msg) { triggerPresenceChanged.Call(msg); });
61     }
62 
63     void SetUp() override
64     {
65         sut = makeTrigger(triggerParams);
66     }
67 
68     static std::vector<LabeledSensorInfo>
69         convertToLabeledSensor(const SensorsInfo& sensorsInfo)
70     {
71         return utils::transform(sensorsInfo, [](const auto& sensorInfo) {
72             const auto& [sensorPath, sensorMetadata] = sensorInfo;
73             return LabeledSensorInfo("service1", sensorPath, sensorMetadata);
74         });
75     }
76 
77     std::unique_ptr<Trigger> makeTrigger(const TriggerParams& params)
78     {
79         thresholdMocks =
80             ThresholdMock::makeThresholds(params.thresholdParams());
81 
82         auto id = std::make_unique<const std::string>(params.id());
83 
84         return std::make_unique<Trigger>(
85             DbusEnvironment::getIoc(), DbusEnvironment::getObjServer(),
86             std::move(id), params.name(), params.triggerActions(),
87             std::make_shared<std::vector<std::string>>(
88                 params.reportIds().begin(), params.reportIds().end()),
89             std::vector<std::shared_ptr<interfaces::Threshold>>(thresholdMocks),
90             *triggerManagerMockPtr, storageMock, *triggerFactoryMockPtr,
91             SensorMock::makeSensorMocks(params.sensors()));
92     }
93 
94     static interfaces::JsonStorage::FilePath to_file_path(std::string name)
95     {
96         return interfaces::JsonStorage::FilePath(
97             std::to_string(std::hash<std::string>{}(name)));
98     }
99 
100     template <class T>
101     static T getProperty(const std::string& path, const std::string& property)
102     {
103         return DbusEnvironment::getProperty<T>(path, Trigger::triggerIfaceName,
104                                                property);
105     }
106 
107     template <class T>
108     static boost::system::error_code setProperty(const std::string& path,
109                                                  const std::string& property,
110                                                  const T& newValue)
111     {
112         return DbusEnvironment::setProperty<T>(path, Trigger::triggerIfaceName,
113                                                property, newValue);
114     }
115 
116     boost::system::error_code deleteTrigger(const std::string& path)
117     {
118         std::promise<boost::system::error_code> methodPromise;
119         DbusEnvironment::getBus()->async_method_call(
120             [&methodPromise](boost::system::error_code ec) {
121                 methodPromise.set_value(ec);
122             },
123             DbusEnvironment::serviceName(), path, Trigger::deleteIfaceName,
124             "Delete");
125         return DbusEnvironment::waitForFuture(methodPromise.get_future());
126     }
127 };
128 
129 TEST_F(TestTrigger, checkIfPropertiesAreSet)
130 {
131     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "Name"),
132                 Eq(triggerParams.name()));
133     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"), Eq(true));
134     EXPECT_THAT(
135         getProperty<std::vector<std::string>>(sut->getPath(), "TriggerActions"),
136         Eq(utils::transform(
137             triggerParams.triggerActions(),
138             [](const auto& action) { return actionToString(action); })));
139     EXPECT_THAT((getProperty<SensorsInfo>(sut->getPath(), "Sensors")),
140                 Eq(utils::fromLabeledSensorsInfo(triggerParams.sensors())));
141     EXPECT_THAT(
142         getProperty<std::vector<object_path>>(sut->getPath(), "Reports"),
143         Eq(triggerParams.reports()));
144     EXPECT_THAT(
145         getProperty<bool>(sut->getPath(), "Discrete"),
146         Eq(isTriggerThresholdDiscrete(triggerParams.thresholdParams())));
147     EXPECT_THAT(
148         getProperty<TriggerThresholdParams>(sut->getPath(), "Thresholds"),
149         Eq(std::visit(utils::FromLabeledThresholdParamConversion(),
150                       triggerParams.thresholdParams())));
151 }
152 
153 TEST_F(TestTrigger, checkBasicGetters)
154 {
155     EXPECT_THAT(sut->getId(), Eq(triggerParams.id()));
156     EXPECT_THAT(sut->getPath(),
157                 Eq(utils::constants::triggerDirPath.str + triggerParams.id()));
158 }
159 
160 TEST_F(TestTrigger, setPropertyNameToCorrectValue)
161 {
162     std::string name = "custom name 1234 %^#5";
163     EXPECT_THAT(setProperty(sut->getPath(), "Name", name),
164                 Eq(boost::system::errc::success));
165     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "Name"), Eq(name));
166 }
167 
168 TEST_F(TestTrigger, setPropertyReportNames)
169 {
170     std::vector<object_path> newNames = {
171         utils::constants::reportDirPath / "abc",
172         utils::constants::reportDirPath / "one",
173         utils::constants::reportDirPath / "prefix" / "two"};
174     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newNames),
175                 Eq(boost::system::errc::success));
176     EXPECT_THAT(
177         getProperty<std::vector<object_path>>(sut->getPath(), "Reports"),
178         Eq(newNames));
179 }
180 
181 TEST_F(TestTrigger, sendsUpdateWhenReportNamesChanges)
182 {
183     std::vector<object_path> newPropertyVal = {
184         utils::constants::reportDirPath / "abc",
185         utils::constants::reportDirPath / "one",
186         utils::constants::reportDirPath / "two"};
187 
188     EXPECT_CALL(triggerPresenceChanged,
189                 Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
190                                UnorderedElementsAre("abc", "one", "two"))));
191 
192     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
193                 Eq(boost::system::errc::success));
194 }
195 
196 TEST_F(TestTrigger, sendsUpdateWhenReportNamesChangesToSameValue)
197 {
198     const std::vector<object_path> newPropertyVal = triggerParams.reports();
199 
200     EXPECT_CALL(
201         triggerPresenceChanged,
202         Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
203                        UnorderedElementsAreArray(triggerParams.reportIds()))));
204 
205     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
206                 Eq(boost::system::errc::success));
207 }
208 
209 TEST_F(TestTrigger,
210        DISABLED_settingPropertyReportNamesThrowsExceptionWhenDuplicateReportIds)
211 {
212     std::vector<object_path> newPropertyVal{
213         utils::constants::reportDirPath / "report1",
214         utils::constants::reportDirPath / "report2",
215         utils::constants::reportDirPath / "report1"};
216 
217     EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
218 
219     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
220                 Eq(boost::system::errc::invalid_argument));
221 }
222 
223 TEST_F(
224     TestTrigger,
225     DISABLED_settingPropertyReportNamesThrowsExceptionWhenReportWithTooManyPrefixes)
226 {
227     std::vector<object_path> newPropertyVal{
228         object_path("/xyz/openbmc_project/Telemetry/Reports/P1/P2/MyReport")};
229 
230     EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
231 
232     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
233                 Eq(boost::system::errc::invalid_argument));
234 }
235 
236 TEST_F(TestTrigger,
237        DISABLED_settingPropertyReportNamesThrowsExceptionWhenReportWithBadPath)
238 {
239     std::vector<object_path> newPropertyVal{
240         object_path("/xyz/openbmc_project/Telemetry/NotReports/MyReport")};
241 
242     EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
243 
244     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
245                 Eq(boost::system::errc::invalid_argument));
246 }
247 
248 TEST_F(TestTrigger, setPropertySensors)
249 {
250     EXPECT_CALL(*triggerFactoryMockPtr, updateSensors(_, _));
251     for (const auto& threshold : thresholdMocks)
252     {
253         auto thresholdMockPtr =
254             std::dynamic_pointer_cast<NiceMock<ThresholdMock>>(threshold);
255         EXPECT_CALL(*thresholdMockPtr, updateSensors(_));
256     }
257     SensorsInfo newSensors(
258         {std::make_pair(object_path("/abc/def"), "metadata")});
259     EXPECT_THAT(setProperty(sut->getPath(), "Sensors", newSensors),
260                 Eq(boost::system::errc::success));
261 }
262 
263 TEST_F(TestTrigger, setPropertyThresholds)
264 {
265     EXPECT_CALL(*triggerFactoryMockPtr, updateThresholds(_, _, _, _, _, _));
266     TriggerThresholdParams newThresholds =
267         std::vector<discrete::ThresholdParam>(
268             {std::make_tuple("discrete threshold", "OK", 10, "12.3")});
269     EXPECT_THAT(setProperty(sut->getPath(), "Thresholds", newThresholds),
270                 Eq(boost::system::errc::success));
271 }
272 
273 TEST_F(TestTrigger, checkIfNumericCoversionsAreGood)
274 {
275     const auto& labeledParamsBase =
276         std::get<std::vector<numeric::LabeledThresholdParam>>(
277             triggerParams.thresholdParams());
278     const auto paramsToCheck =
279         std::visit(utils::FromLabeledThresholdParamConversion(),
280                    triggerParams.thresholdParams());
281     const auto labeledParamsToCheck =
282         std::get<std::vector<numeric::LabeledThresholdParam>>(std::visit(
283             utils::ToLabeledThresholdParamConversion(), paramsToCheck));
284 
285     for (const auto& [tocheck, base] :
286          boost::combine(labeledParamsToCheck, labeledParamsBase))
287     {
288         EXPECT_THAT(tocheck.at_label<utils::tstring::Type>(),
289                     Eq(base.at_label<utils::tstring::Type>()));
290         EXPECT_THAT(tocheck.at_label<utils::tstring::Direction>(),
291                     Eq(base.at_label<utils::tstring::Direction>()));
292         EXPECT_THAT(tocheck.at_label<utils::tstring::DwellTime>(),
293                     Eq(base.at_label<utils::tstring::DwellTime>()));
294         EXPECT_THAT(tocheck.at_label<utils::tstring::ThresholdValue>(),
295                     Eq(base.at_label<utils::tstring::ThresholdValue>()));
296     }
297 }
298 
299 TEST_F(TestTrigger, checkIfDiscreteCoversionsAreGood)
300 {
301     const auto& labeledParamsBase =
302         std::get<std::vector<discrete::LabeledThresholdParam>>(
303             triggerDiscreteParams.thresholdParams());
304     const auto paramsToCheck =
305         std::visit(utils::FromLabeledThresholdParamConversion(),
306                    triggerDiscreteParams.thresholdParams());
307     const auto labeledParamsToCheck =
308         std::get<std::vector<discrete::LabeledThresholdParam>>(std::visit(
309             utils::ToLabeledThresholdParamConversion(), paramsToCheck));
310 
311     for (const auto& [tocheck, base] :
312          boost::combine(labeledParamsToCheck, labeledParamsBase))
313     {
314         EXPECT_THAT(tocheck.at_label<utils::tstring::UserId>(),
315                     Eq(base.at_label<utils::tstring::UserId>()));
316         EXPECT_THAT(tocheck.at_label<utils::tstring::Severity>(),
317                     Eq(base.at_label<utils::tstring::Severity>()));
318         EXPECT_THAT(tocheck.at_label<utils::tstring::DwellTime>(),
319                     Eq(base.at_label<utils::tstring::DwellTime>()));
320         EXPECT_THAT(tocheck.at_label<utils::tstring::ThresholdValue>(),
321                     Eq(base.at_label<utils::tstring::ThresholdValue>()));
322     }
323 }
324 
325 TEST_F(TestTrigger, deleteTrigger)
326 {
327     EXPECT_CALL(storageMock, remove(to_file_path(sut->getId())));
328     EXPECT_CALL(*triggerManagerMockPtr, removeTrigger(sut.get()));
329 
330     auto ec = deleteTrigger(sut->getPath());
331     EXPECT_THAT(ec, Eq(boost::system::errc::success));
332 }
333 
334 TEST_F(TestTrigger, sendUpdateWhenTriggerIsDeleted)
335 {
336     EXPECT_CALL(triggerPresenceChanged,
337                 Call(FieldsAre(messages::Presence::Removed, triggerParams.id(),
338                                UnorderedElementsAre())));
339 
340     auto ec = deleteTrigger(sut->getPath());
341     EXPECT_THAT(ec, Eq(boost::system::errc::success));
342 }
343 
344 TEST_F(TestTrigger, deletingNonExistingTriggerReturnInvalidRequestDescriptor)
345 {
346     auto ec =
347         deleteTrigger(utils::constants::triggerDirPath.str + "NonExisting"s);
348     EXPECT_THAT(ec.value(), Eq(EBADR));
349 }
350 
351 TEST_F(TestTrigger, settingPersistencyToFalseRemovesTriggerFromStorage)
352 {
353     EXPECT_CALL(storageMock, remove(to_file_path(sut->getId())));
354 
355     bool persistent = false;
356     EXPECT_THAT(setProperty(sut->getPath(), "Persistent", persistent),
357                 Eq(boost::system::errc::success));
358     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"),
359                 Eq(persistent));
360 }
361 
362 class TestTriggerInitialization : public TestTrigger
363 {
364   public:
365     void SetUp() override
366     {}
367 
368     nlohmann::json storedConfiguration;
369 };
370 
371 TEST_F(TestTriggerInitialization,
372        exceptionDuringTriggerStoreDisablesPersistency)
373 {
374     EXPECT_CALL(storageMock, store(_, _))
375         .WillOnce(Throw(std::runtime_error("Generic error!")));
376 
377     sut = makeTrigger(triggerParams);
378 
379     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"), Eq(false));
380 }
381 
382 TEST_F(TestTriggerInitialization, creatingTriggerThrowsExceptionWhenIdIsInvalid)
383 {
384     EXPECT_CALL(storageMock, store(_, _)).Times(0);
385 
386     EXPECT_THROW(makeTrigger(triggerParams.id("inv?lidId")),
387                  sdbusplus::exception::SdBusError);
388 }
389 
390 TEST_F(TestTriggerInitialization, creatingTriggerUpdatesTriggersIdsInReports)
391 {
392     EXPECT_CALL(
393         triggerPresenceChanged,
394         Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
395                        UnorderedElementsAreArray(triggerParams.reportIds()))));
396 
397     sut = makeTrigger(triggerParams);
398 }
399 
400 class TestTriggerStore : public TestTrigger
401 {
402   public:
403     nlohmann::json storedConfiguration;
404     nlohmann::json storedDiscreteConfiguration;
405     std::unique_ptr<Trigger> sutDiscrete;
406 
407     void SetUp() override
408     {
409         ON_CALL(storageMock, store(_, _))
410             .WillByDefault(SaveArg<1>(&storedConfiguration));
411         sut = makeTrigger(triggerParams);
412 
413         ON_CALL(storageMock, store(_, _))
414             .WillByDefault(SaveArg<1>(&storedDiscreteConfiguration));
415         sutDiscrete = makeTrigger(triggerDiscreteParams);
416     }
417 };
418 
419 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerVersion)
420 {
421     ASSERT_THAT(storedConfiguration.at("Version"), Eq(expectedTriggerVersion));
422 }
423 
424 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerId)
425 {
426     ASSERT_THAT(storedConfiguration.at("Id"), Eq(triggerParams.id()));
427 }
428 
429 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerName)
430 {
431     ASSERT_THAT(storedConfiguration.at("Name"), Eq(triggerParams.name()));
432 }
433 
434 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerTriggerActions)
435 {
436     ASSERT_THAT(storedConfiguration.at("TriggerActions"),
437                 Eq(utils::transform(triggerParams.triggerActions(),
438                                     [](const auto& action) {
439                                         return actionToString(action);
440                                     })));
441 }
442 
443 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerReportIds)
444 {
445     ASSERT_THAT(storedConfiguration.at("ReportIds"),
446                 Eq(triggerParams.reportIds()));
447 }
448 
449 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerSensors)
450 {
451     nlohmann::json expectedItem;
452     expectedItem["service"] = "service1";
453     expectedItem["path"] = "/xyz/openbmc_project/sensors/temperature/BMC_Temp";
454     expectedItem["metadata"] = "metadata1";
455 
456     ASSERT_THAT(storedConfiguration.at("Sensors"), ElementsAre(expectedItem));
457 }
458 
459 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerThresholdParams)
460 {
461     nlohmann::json expectedItem0;
462     expectedItem0["type"] = 0;
463     expectedItem0["dwellTime"] = 10;
464     expectedItem0["direction"] = 1;
465     expectedItem0["thresholdValue"] = 0.5;
466 
467     nlohmann::json expectedItem1;
468     expectedItem1["type"] = 3;
469     expectedItem1["dwellTime"] = 10;
470     expectedItem1["direction"] = 2;
471     expectedItem1["thresholdValue"] = 90.2;
472 
473     ASSERT_THAT(storedConfiguration.at("ThresholdParamsDiscriminator"), Eq(0));
474     ASSERT_THAT(storedConfiguration.at("ThresholdParams"),
475                 ElementsAre(expectedItem0, expectedItem1));
476 }
477 
478 TEST_F(TestTriggerStore,
479        settingPersistencyToTrueStoresDiscreteTriggerThresholdParams)
480 {
481     nlohmann::json expectedItem0;
482     expectedItem0["userId"] = "userId";
483     expectedItem0["severity"] = discrete::Severity::warning;
484     expectedItem0["dwellTime"] = 10;
485     expectedItem0["thresholdValue"] = "15.2";
486 
487     nlohmann::json expectedItem1;
488     expectedItem1["userId"] = "userId_2";
489     expectedItem1["severity"] = discrete::Severity::critical;
490     expectedItem1["dwellTime"] = 5;
491     expectedItem1["thresholdValue"] = "32.7";
492 
493     ASSERT_THAT(storedDiscreteConfiguration.at("ThresholdParamsDiscriminator"),
494                 Eq(1));
495     ASSERT_THAT(storedDiscreteConfiguration.at("ThresholdParams"),
496                 ElementsAre(expectedItem0, expectedItem1));
497 }
498